mirror of
https://github.com/Radiquum/AniX.git
synced 2025-09-05 22:15:36 +05:00
anix/feat: finish Filters Modal Component
This commit is contained in:
parent
2a2343fed3
commit
0f1c61b765
5 changed files with 281 additions and 13 deletions
|
@ -51,6 +51,7 @@ export const ENDPOINTS = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
filter: `${API_PREFIX}/filter`,
|
filter: `${API_PREFIX}/filter`,
|
||||||
|
filterTypes: `${API_PREFIX}/type/all`,
|
||||||
search: {
|
search: {
|
||||||
profileList: `${API_PREFIX}/search/profile/list`,
|
profileList: `${API_PREFIX}/search/profile/list`,
|
||||||
profileHistory: `${API_PREFIX}/search/history`,
|
profileHistory: `${API_PREFIX}/search/history`,
|
||||||
|
|
106
app/components/Discovery/Modal/FiltersAgeRatingModal.tsx
Normal file
106
app/components/Discovery/Modal/FiltersAgeRatingModal.tsx
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FilterAgeRatingToString } from "#/api/utils";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Label,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
} from "flowbite-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean;
|
||||||
|
setIsOpen: (isOpen: boolean) => void;
|
||||||
|
ageRatings: number[];
|
||||||
|
setAgeRatings: (lists: number[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FiltersAgeRatingModal = ({
|
||||||
|
isOpen,
|
||||||
|
setIsOpen,
|
||||||
|
ageRatings,
|
||||||
|
setAgeRatings,
|
||||||
|
}: Props) => {
|
||||||
|
const [newAgeRatings, setNewAgeRatings] = useState(ageRatings);
|
||||||
|
|
||||||
|
function toggleRating(number: number) {
|
||||||
|
if (newAgeRatings.includes(number)) {
|
||||||
|
setNewAgeRatings(newAgeRatings.filter((rating) => rating != number));
|
||||||
|
} else {
|
||||||
|
setNewAgeRatings([...newAgeRatings, number]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setNewAgeRatings(ageRatings);
|
||||||
|
}, [ageRatings]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal show={isOpen} onClose={() => setIsOpen(false)} dismissible>
|
||||||
|
<ModalHeader>Выберите списки</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
{Object.entries(FilterAgeRatingToString).map(([key, value]) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
key={`filter-age-rating-${value}`}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
id={`filter-age-rating-${value}`}
|
||||||
|
onChange={() => toggleRating(Number(key))}
|
||||||
|
checked={newAgeRatings.includes(Number(key))}
|
||||||
|
color="blue"
|
||||||
|
/>
|
||||||
|
<Label htmlFor={`filter-age-rating-${value}`}>{value}</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setAgeRatings([]);
|
||||||
|
setNewAgeRatings([]);
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
|
color="red"
|
||||||
|
>
|
||||||
|
Сбросить
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setAgeRatings(newAgeRatings);
|
||||||
|
setNewAgeRatings([]);
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
|
color="blue"
|
||||||
|
>
|
||||||
|
Применить
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (
|
||||||
|
newAgeRatings.length !=
|
||||||
|
Object.keys(FilterAgeRatingToString).length
|
||||||
|
) {
|
||||||
|
setNewAgeRatings(
|
||||||
|
Object.keys(FilterAgeRatingToString).map((key) => Number(key))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setNewAgeRatings([]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
color="light"
|
||||||
|
>
|
||||||
|
{newAgeRatings.length >= Object.keys(FilterAgeRatingToString).length ?
|
||||||
|
"Снять все"
|
||||||
|
: "Выбрать все"}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Filter,
|
Filter,
|
||||||
|
FilterAgeRatingToString,
|
||||||
FilterCategoryIdToString,
|
FilterCategoryIdToString,
|
||||||
FilterCountry,
|
FilterCountry,
|
||||||
FilterDefault,
|
FilterDefault,
|
||||||
|
@ -14,6 +15,7 @@ import {
|
||||||
FilterStatusIdToString,
|
FilterStatusIdToString,
|
||||||
FilterStudio,
|
FilterStudio,
|
||||||
FilterYear,
|
FilterYear,
|
||||||
|
tryCatchAPI,
|
||||||
} from "#/api/utils";
|
} from "#/api/utils";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
@ -24,10 +26,14 @@ import {
|
||||||
ModalFooter,
|
ModalFooter,
|
||||||
ModalHeader,
|
ModalHeader,
|
||||||
} from "flowbite-react";
|
} from "flowbite-react";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { FiltersGenreModal } from "./FiltersGenreModal";
|
import { FiltersGenreModal } from "./FiltersGenreModal";
|
||||||
import { useUserStore } from "#/store/auth";
|
import { useUserStore } from "#/store/auth";
|
||||||
import { FiltersListExcludeModal } from "./FiltersListExcludeModal";
|
import { FiltersListExcludeModal } from "./FiltersListExcludeModal";
|
||||||
|
import { ENDPOINTS } from "#/api/config";
|
||||||
|
import { FiltersTypesModal } from "./FiltersTypesModal";
|
||||||
|
import { FiltersAgeRatingModal } from "./FiltersAgeRatingModal";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
type ModalProps = {
|
type ModalProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
@ -37,20 +43,46 @@ type ModalProps = {
|
||||||
|
|
||||||
export const FiltersModal = ({ isOpen, setIsOpen, filter }: ModalProps) => {
|
export const FiltersModal = ({ isOpen, setIsOpen, filter }: ModalProps) => {
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const [newFilter, setNewFilter] = useState(filter || FilterDefault);
|
const [newFilter, setNewFilter] = useState(filter || FilterDefault);
|
||||||
const [isGenreModalOpen, setIsGenreModalOpen] = useState(false);
|
const [isGenreModalOpen, setIsGenreModalOpen] = useState(false);
|
||||||
const [isListExcludeModalOpen, setIsListExcludeModalOpen] = useState(false);
|
const [isListExcludeModalOpen, setIsListExcludeModalOpen] = useState(false);
|
||||||
|
const [isTypeModalOpen, setIsTypeModalOpen] = useState(false);
|
||||||
|
const [isAgeRatingModalOpen, setIsAgeRatingModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const [types, setTypes] = useState([]);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const { data, error } = await tryCatchAPI(fetch(ENDPOINTS.filterTypes));
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
setError(error);
|
||||||
|
} else {
|
||||||
|
setTypes(data.types);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
function saveGenres(genres, is_genres_exclude_mode_enabled) {
|
function saveGenres(genres, is_genres_exclude_mode_enabled) {
|
||||||
setNewFilter({ ...newFilter, genres, is_genres_exclude_mode_enabled });
|
setNewFilter({ ...newFilter, genres, is_genres_exclude_mode_enabled });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveFilter() {
|
||||||
|
const _filter = JSON.stringify(newFilter);
|
||||||
|
router.push(`/discovery/filter?filter=${_filter}`);
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
// show={isOpen}
|
show={isOpen}
|
||||||
show={true}
|
|
||||||
onClose={() => setIsOpen(false)}
|
onClose={() => setIsOpen(false)}
|
||||||
size="4xl"
|
size="4xl"
|
||||||
dismissible
|
dismissible
|
||||||
|
@ -163,11 +195,15 @@ export const FiltersModal = ({ isOpen, setIsOpen, filter }: ModalProps) => {
|
||||||
<Button
|
<Button
|
||||||
color={"blue"}
|
color={"blue"}
|
||||||
className="w-full min-h-10 h-fit"
|
className="w-full min-h-10 h-fit"
|
||||||
// onClick={() => setIsGenreModalOpen(true)}
|
onClick={() => setIsTypeModalOpen(true)}
|
||||||
>
|
>
|
||||||
{/* {newFilter.genres.length > 0 ? */}
|
{error ?
|
||||||
{/* newFilter.genres.join(", ") */}
|
error.message
|
||||||
{/* : "Неважно"} */}
|
: newFilter.types.length > 0 ?
|
||||||
|
newFilter.types
|
||||||
|
.map((type) => types.find((t) => t.id === type).name)
|
||||||
|
.join(", ")
|
||||||
|
: "Неважно"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
@ -443,11 +479,13 @@ export const FiltersModal = ({ isOpen, setIsOpen, filter }: ModalProps) => {
|
||||||
<Button
|
<Button
|
||||||
color={"blue"}
|
color={"blue"}
|
||||||
className="w-full min-h-10 h-fit"
|
className="w-full min-h-10 h-fit"
|
||||||
// onClick={() => setIsGenreModalOpen(true)}
|
onClick={() => setIsAgeRatingModalOpen(true)}
|
||||||
>
|
>
|
||||||
{/* {newFilter.genres.length > 0 ?
|
{newFilter.age_ratings.length > 0 ?
|
||||||
newFilter.genres.join(", ")
|
newFilter.age_ratings
|
||||||
: "Неважно"} */}
|
.map((age_rating) => FilterAgeRatingToString[age_rating])
|
||||||
|
.join(", ")
|
||||||
|
: "Неважно"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
@ -476,7 +514,11 @@ export const FiltersModal = ({ isOpen, setIsOpen, filter }: ModalProps) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter></ModalFooter>
|
<ModalFooter>
|
||||||
|
<Button color="blue" onClick={saveFilter}>
|
||||||
|
Применить
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
<FiltersGenreModal
|
<FiltersGenreModal
|
||||||
isOpen={isGenreModalOpen}
|
isOpen={isGenreModalOpen}
|
||||||
|
@ -493,6 +535,21 @@ export const FiltersModal = ({ isOpen, setIsOpen, filter }: ModalProps) => {
|
||||||
setNewFilter({ ...newFilter, profile_list_exclusions })
|
setNewFilter({ ...newFilter, profile_list_exclusions })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<FiltersTypesModal
|
||||||
|
isOpen={isTypeModalOpen}
|
||||||
|
setIsOpen={setIsTypeModalOpen}
|
||||||
|
typesData={types}
|
||||||
|
types={newFilter.types}
|
||||||
|
setTypes={(types) => setNewFilter({ ...newFilter, types })}
|
||||||
|
/>
|
||||||
|
<FiltersAgeRatingModal
|
||||||
|
isOpen={isAgeRatingModalOpen}
|
||||||
|
setIsOpen={setIsAgeRatingModalOpen}
|
||||||
|
ageRatings={newFilter.age_ratings}
|
||||||
|
setAgeRatings={(age_ratings) =>
|
||||||
|
setNewFilter({ ...newFilter, age_ratings })
|
||||||
|
}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
100
app/components/Discovery/Modal/FiltersTypesModal.tsx
Normal file
100
app/components/Discovery/Modal/FiltersTypesModal.tsx
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Label,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
} from "flowbite-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean;
|
||||||
|
setIsOpen: (isOpen: boolean) => void;
|
||||||
|
typesData: any[];
|
||||||
|
types: number[];
|
||||||
|
setTypes: (types: number[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FiltersTypesModal = ({
|
||||||
|
isOpen,
|
||||||
|
setIsOpen,
|
||||||
|
typesData,
|
||||||
|
types,
|
||||||
|
setTypes,
|
||||||
|
}: Props) => {
|
||||||
|
const [newTypes, setNewTypes] = useState(types);
|
||||||
|
|
||||||
|
function toggleType(number: number) {
|
||||||
|
if (newTypes.includes(number)) {
|
||||||
|
setNewTypes(newTypes.filter((list) => list != number));
|
||||||
|
} else {
|
||||||
|
setNewTypes([...newTypes, number]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setNewTypes(types);
|
||||||
|
}, [types]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal show={isOpen} onClose={() => setIsOpen(false)} dismissible>
|
||||||
|
<ModalHeader>Выберите списки</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
{typesData.map((item) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
key={`filter-types-${item.name}`}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
id={`filter-types-${item.name}`}
|
||||||
|
onChange={() => toggleType(Number(item.id))}
|
||||||
|
checked={newTypes.includes(Number(item.id))}
|
||||||
|
color="blue"
|
||||||
|
/>
|
||||||
|
<Label htmlFor={`filter-types-${item.name}`}>{item.name}</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setTypes([]);
|
||||||
|
setNewTypes([]);
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
|
color="red"
|
||||||
|
>
|
||||||
|
Сбросить
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setTypes(newTypes);
|
||||||
|
setNewTypes([]);
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
|
color="blue"
|
||||||
|
>
|
||||||
|
Применить
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (newTypes.length != typesData.length) {
|
||||||
|
setNewTypes(typesData.map((item) => Number(item.id)));
|
||||||
|
} else {
|
||||||
|
setNewTypes([]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
color="light"
|
||||||
|
>
|
||||||
|
{newTypes.length >= typesData.length ? "Снять все" : "Выбрать все"}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
|
@ -45,7 +45,11 @@ export const DiscoverPage = () => {
|
||||||
<span className="flex-shrink-0 inline-block w-8 h-8 mr-2 iconify mdi--collections-bookmark"></span>
|
<span className="flex-shrink-0 inline-block w-8 h-8 mr-2 iconify mdi--collections-bookmark"></span>
|
||||||
<span>Коллекции</span>
|
<span>Коллекции</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="xl" color="green">
|
<Button
|
||||||
|
size="xl"
|
||||||
|
color="green"
|
||||||
|
onClick={() => setFiltersModalOpen(true)}
|
||||||
|
>
|
||||||
<span className="flex-shrink-0 inline-block w-8 h-8 mr-2 iconify mdi--mixer-settings"></span>
|
<span className="flex-shrink-0 inline-block w-8 h-8 mr-2 iconify mdi--mixer-settings"></span>
|
||||||
<span>Фильтр</span>
|
<span>Фильтр</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue