anix/feat: finish Filters Modal Component

This commit is contained in:
Kentai Radiquum 2025-08-28 20:52:43 +05:00
parent 2a2343fed3
commit 0f1c61b765
Signed by: Radiquum
GPG key ID: 858E8EE696525EED
5 changed files with 281 additions and 13 deletions

View file

@ -51,6 +51,7 @@ export const ENDPOINTS = {
}
},
filter: `${API_PREFIX}/filter`,
filterTypes: `${API_PREFIX}/type/all`,
search: {
profileList: `${API_PREFIX}/search/profile/list`,
profileHistory: `${API_PREFIX}/search/history`,

View 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>
);
};

View file

@ -2,6 +2,7 @@
import {
Filter,
FilterAgeRatingToString,
FilterCategoryIdToString,
FilterCountry,
FilterDefault,
@ -14,6 +15,7 @@ import {
FilterStatusIdToString,
FilterStudio,
FilterYear,
tryCatchAPI,
} from "#/api/utils";
import {
Button,
@ -24,10 +26,14 @@ import {
ModalFooter,
ModalHeader,
} from "flowbite-react";
import { useState } from "react";
import { useEffect, useState } from "react";
import { FiltersGenreModal } from "./FiltersGenreModal";
import { useUserStore } from "#/store/auth";
import { FiltersListExcludeModal } from "./FiltersListExcludeModal";
import { ENDPOINTS } from "#/api/config";
import { FiltersTypesModal } from "./FiltersTypesModal";
import { FiltersAgeRatingModal } from "./FiltersAgeRatingModal";
import { useRouter } from "next/navigation";
type ModalProps = {
isOpen: boolean;
@ -37,20 +43,46 @@ type ModalProps = {
export const FiltersModal = ({ isOpen, setIsOpen, filter }: ModalProps) => {
const userStore = useUserStore();
const router = useRouter();
const [newFilter, setNewFilter] = useState(filter || FilterDefault);
const [isGenreModalOpen, setIsGenreModalOpen] = 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) {
setNewFilter({ ...newFilter, genres, is_genres_exclude_mode_enabled });
}
function saveFilter() {
const _filter = JSON.stringify(newFilter);
router.push(`/discovery/filter?filter=${_filter}`);
setIsOpen(false);
}
return (
<>
<Modal
// show={isOpen}
show={true}
show={isOpen}
onClose={() => setIsOpen(false)}
size="4xl"
dismissible
@ -163,11 +195,15 @@ export const FiltersModal = ({ isOpen, setIsOpen, filter }: ModalProps) => {
<Button
color={"blue"}
className="w-full min-h-10 h-fit"
// onClick={() => setIsGenreModalOpen(true)}
onClick={() => setIsTypeModalOpen(true)}
>
{/* {newFilter.genres.length > 0 ? */}
{/* newFilter.genres.join(", ") */}
{/* : "Неважно"} */}
{error ?
error.message
: newFilter.types.length > 0 ?
newFilter.types
.map((type) => types.find((t) => t.id === type).name)
.join(", ")
: "Неважно"}
</Button>
</div>
<div className="space-y-2">
@ -443,11 +479,13 @@ export const FiltersModal = ({ isOpen, setIsOpen, filter }: ModalProps) => {
<Button
color={"blue"}
className="w-full min-h-10 h-fit"
// onClick={() => setIsGenreModalOpen(true)}
onClick={() => setIsAgeRatingModalOpen(true)}
>
{/* {newFilter.genres.length > 0 ?
newFilter.genres.join(", ")
: "Неважно"} */}
{newFilter.age_ratings.length > 0 ?
newFilter.age_ratings
.map((age_rating) => FilterAgeRatingToString[age_rating])
.join(", ")
: "Неважно"}
</Button>
</div>
<div className="space-y-2">
@ -476,7 +514,11 @@ export const FiltersModal = ({ isOpen, setIsOpen, filter }: ModalProps) => {
</div>
</div>
</ModalBody>
<ModalFooter></ModalFooter>
<ModalFooter>
<Button color="blue" onClick={saveFilter}>
Применить
</Button>
</ModalFooter>
</Modal>
<FiltersGenreModal
isOpen={isGenreModalOpen}
@ -493,6 +535,21 @@ export const FiltersModal = ({ isOpen, setIsOpen, filter }: ModalProps) => {
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 })
}
/>
</>
);
};

View 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>
);
};

View file

@ -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>Коллекции</span>
</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>Фильтр</span>
</Button>