mirror of
https://github.com/Radiquum/AniX.git
synced 2025-04-07 00:34:41 +00:00
feat: add user vote change for a release in a rating widget
This commit is contained in:
parent
8f6319ab0d
commit
c947f36289
3 changed files with 215 additions and 81 deletions
4
TODO.md
4
TODO.md
|
@ -66,9 +66,9 @@
|
||||||
- [X] Добавление \ Удаление аниме в\из списков закладок и избранных
|
- [X] Добавление \ Удаление аниме в\из списков закладок и избранных
|
||||||
- [X] Связанные релизы
|
- [X] Связанные релизы
|
||||||
- [X] Просмотр страницы всех связанных релизов
|
- [X] Просмотр страницы всех связанных релизов
|
||||||
- [ ] Оценка тайтла
|
- [X] Оценка тайтла
|
||||||
- [X] Просмотр оценки
|
- [X] Просмотр оценки
|
||||||
- [ ] Оценивание и изменение своей оценки (как Modal)
|
- [X] Оценивание и изменение своей оценки (как Modal)
|
||||||
|
|
||||||
## Стиль
|
## Стиль
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,11 @@ import {
|
||||||
Flowbite,
|
Flowbite,
|
||||||
Button,
|
Button,
|
||||||
CustomFlowbiteTheme,
|
CustomFlowbiteTheme,
|
||||||
|
Modal,
|
||||||
} from "flowbite-react";
|
} from "flowbite-react";
|
||||||
import { numberDeclension } from "#/api/utils";
|
import { numberDeclension } from "#/api/utils";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { ENDPOINTS } from "#/api/config";
|
||||||
|
|
||||||
const RatingTheme: CustomFlowbiteTheme = {
|
const RatingTheme: CustomFlowbiteTheme = {
|
||||||
ratingAdvanced: {
|
ratingAdvanced: {
|
||||||
|
@ -15,6 +18,7 @@ const RatingTheme: CustomFlowbiteTheme = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
export const ReleaseInfoRating = (props: {
|
export const ReleaseInfoRating = (props: {
|
||||||
|
release_id: number;
|
||||||
grade: number;
|
grade: number;
|
||||||
token: string | null;
|
token: string | null;
|
||||||
votes: {
|
votes: {
|
||||||
|
@ -27,88 +31,217 @@ export const ReleaseInfoRating = (props: {
|
||||||
user: number | null;
|
user: number | null;
|
||||||
};
|
};
|
||||||
}) => {
|
}) => {
|
||||||
|
const [isRatingModalOpen, setIsRatingModalOpen] = useState(false);
|
||||||
|
const [vote, setVote] = useState(props.votes.user);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<>
|
||||||
<div className="flex flex-col gap-2 lg:items-center lg:flex-row">
|
<Card>
|
||||||
<Rating>
|
<div className="flex flex-col gap-2 sm:items-center sm:flex-row">
|
||||||
<Rating.Star />
|
<Rating>
|
||||||
<p className="ml-2 text-sm font-bold dark:text-white">
|
<Rating.Star />
|
||||||
{props.grade.toFixed(2)} из 5
|
<p className="ml-2 text-sm font-bold dark:text-white">
|
||||||
</p>
|
{props.grade.toFixed(2)} из 5
|
||||||
</Rating>
|
</p>
|
||||||
{props.token && (
|
</Rating>
|
||||||
<>
|
{props.token && (
|
||||||
<span className="mx-1.5 h-1 w-1 rounded-full bg-gray-500 dark:bg-gray-400 hidden lg:block" />
|
<>
|
||||||
{props.votes.user ? (
|
<span className="mx-1.5 h-1 w-1 rounded-full bg-gray-500 dark:bg-gray-400 hidden lg:block" />
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<p className="text-sm font-medium text-gray-500 dark:text-gray-400">
|
{vote ? (
|
||||||
ваша оценка: {props.votes.user}
|
<>
|
||||||
</p>
|
<p className="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||||
<Button
|
ваша оценка: {vote}
|
||||||
size={"xs"}
|
</p>
|
||||||
className="text-gray-500 border border-gray-600 rounded-full hover:bg-black hover:text-white hover:border-black dark:text-gray-400 dark:border-gray-500"
|
<Button
|
||||||
color="inline"
|
size={"xs"}
|
||||||
>
|
className="text-gray-500 border border-gray-600 rounded-full hover:bg-black hover:text-white hover:border-black dark:text-gray-400 dark:border-gray-500"
|
||||||
изменить
|
color="inline"
|
||||||
</Button>
|
onClick={() => setIsRatingModalOpen(true)}
|
||||||
|
>
|
||||||
|
изменить
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
size={"xs"}
|
||||||
|
className="text-gray-500 border border-gray-600 rounded-full hover:bg-black hover:text-white hover:border-black dark:text-gray-400 dark:border-gray-500"
|
||||||
|
color="inline"
|
||||||
|
onClick={() => setIsRatingModalOpen(true)}
|
||||||
|
>
|
||||||
|
оценить
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</>
|
||||||
<Button
|
)}
|
||||||
size={"xs"}
|
</div>
|
||||||
className="text-gray-500 border border-gray-600 rounded-full hover:bg-black hover:text-white hover:border-black dark:text-gray-400 dark:border-gray-500"
|
<p className="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||||
color="inline"
|
{props.votes.total}{" "}
|
||||||
>
|
{numberDeclension(props.votes.total, "голос", "голоса", "голосов")}
|
||||||
оценить
|
</p>
|
||||||
</Button>
|
<Flowbite theme={{ theme: RatingTheme }}>
|
||||||
|
<Rating.Advanced
|
||||||
|
percentFilled={Math.floor(
|
||||||
|
(props.votes["5"] / props.votes.total) * 100
|
||||||
)}
|
)}
|
||||||
</>
|
className="mb-2"
|
||||||
)}
|
>
|
||||||
</div>
|
5
|
||||||
<p className="text-sm font-medium text-gray-500 dark:text-gray-400">
|
</Rating.Advanced>
|
||||||
{props.votes.total}{" "}
|
<Rating.Advanced
|
||||||
{numberDeclension(props.votes.total, "голос", "голоса", "голосов")}
|
percentFilled={Math.floor(
|
||||||
</p>
|
(props.votes["4"] / props.votes.total) * 100
|
||||||
<Flowbite theme={{ theme: RatingTheme }}>
|
)}
|
||||||
<Rating.Advanced
|
className="mb-2"
|
||||||
percentFilled={Math.floor(
|
>
|
||||||
(props.votes["5"] / props.votes.total) * 100
|
4
|
||||||
)}
|
</Rating.Advanced>
|
||||||
className="mb-2"
|
<Rating.Advanced
|
||||||
>
|
percentFilled={Math.floor(
|
||||||
5
|
(props.votes["3"] / props.votes.total) * 100
|
||||||
</Rating.Advanced>
|
)}
|
||||||
<Rating.Advanced
|
className="mb-2"
|
||||||
percentFilled={Math.floor(
|
>
|
||||||
(props.votes["4"] / props.votes.total) * 100
|
3
|
||||||
)}
|
</Rating.Advanced>
|
||||||
className="mb-2"
|
<Rating.Advanced
|
||||||
>
|
percentFilled={Math.floor(
|
||||||
4
|
(props.votes["2"] / props.votes.total) * 100
|
||||||
</Rating.Advanced>
|
)}
|
||||||
<Rating.Advanced
|
className="mb-2"
|
||||||
percentFilled={Math.floor(
|
>
|
||||||
(props.votes["3"] / props.votes.total) * 100
|
2
|
||||||
)}
|
</Rating.Advanced>
|
||||||
className="mb-2"
|
<Rating.Advanced
|
||||||
>
|
percentFilled={Math.floor(
|
||||||
3
|
(props.votes["1"] / props.votes.total) * 100
|
||||||
</Rating.Advanced>
|
)}
|
||||||
<Rating.Advanced
|
>
|
||||||
percentFilled={Math.floor(
|
1
|
||||||
(props.votes["2"] / props.votes.total) * 100
|
</Rating.Advanced>
|
||||||
)}
|
</Flowbite>
|
||||||
className="mb-2"
|
</Card>
|
||||||
>
|
<ReleaseInfoRatingModal
|
||||||
2
|
isOpen={isRatingModalOpen}
|
||||||
</Rating.Advanced>
|
setIsOpen={setIsRatingModalOpen}
|
||||||
<Rating.Advanced
|
token={props.token}
|
||||||
percentFilled={Math.floor(
|
vote={vote}
|
||||||
(props.votes["1"] / props.votes.total) * 100
|
release_id={props.release_id}
|
||||||
)}
|
setUserVote={setVote}
|
||||||
>
|
/>
|
||||||
1
|
</>
|
||||||
</Rating.Advanced>
|
);
|
||||||
</Flowbite>
|
};
|
||||||
</Card>
|
|
||||||
|
const ReleaseInfoRatingModal = (props: {
|
||||||
|
isOpen: boolean;
|
||||||
|
setIsOpen: any;
|
||||||
|
setUserVote: any;
|
||||||
|
token: string | null;
|
||||||
|
vote: number;
|
||||||
|
release_id: number;
|
||||||
|
}) => {
|
||||||
|
const [curElement, setCurElement] = useState(props.vote);
|
||||||
|
const [vote, setVote] = useState(props.vote);
|
||||||
|
const [isSending, setIsSending] = useState(false);
|
||||||
|
|
||||||
|
async function _sendVote(
|
||||||
|
action: "delete" | "add" = "add",
|
||||||
|
vote: number | null = null
|
||||||
|
) {
|
||||||
|
let url = `${ENDPOINTS.release.info}/vote/${action}/${props.release_id}`;
|
||||||
|
if (action === "add") {
|
||||||
|
url += `/${vote}`;
|
||||||
|
}
|
||||||
|
url += `?token=${props.token}`;
|
||||||
|
|
||||||
|
fetch(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _setVote(
|
||||||
|
action: "delete" | "add" = "add",
|
||||||
|
vote: number | null = null
|
||||||
|
) {
|
||||||
|
if (props.token) {
|
||||||
|
_sendVote(action, vote);
|
||||||
|
setVote(vote);
|
||||||
|
props.setUserVote(vote);
|
||||||
|
props.setIsOpen(false);
|
||||||
|
setIsSending(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
dismissible
|
||||||
|
show={props.isOpen}
|
||||||
|
onClose={() => props.setIsOpen(false)}
|
||||||
|
>
|
||||||
|
<Modal.Header>Оценка</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<div>
|
||||||
|
<div className="block sm:hidden">
|
||||||
|
<Rating size="md" className="justify-center">
|
||||||
|
{[1, 2, 3, 4, 5].map((element, index) => (
|
||||||
|
<Button
|
||||||
|
key={`rating-md-${element}`}
|
||||||
|
color={"inline"}
|
||||||
|
onMouseOver={() => setCurElement(element)}
|
||||||
|
onMouseOut={() => setCurElement(0)}
|
||||||
|
onClick={() => setVote(element)}
|
||||||
|
>
|
||||||
|
<Rating.Star
|
||||||
|
filled={index + 1 <= curElement || index + 1 <= vote}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Rating>
|
||||||
|
</div>
|
||||||
|
<div className="hidden sm:block">
|
||||||
|
<Rating size="lg" className="justify-center">
|
||||||
|
{[1, 2, 3, 4, 5].map((element, index) => (
|
||||||
|
<Button
|
||||||
|
key={`rating-lg-${element}`}
|
||||||
|
color={"inline"}
|
||||||
|
onMouseOver={() => setCurElement(element)}
|
||||||
|
onMouseOut={() => setCurElement(0)}
|
||||||
|
onClick={() => setVote(element)}
|
||||||
|
>
|
||||||
|
<Rating.Star
|
||||||
|
filled={index + 1 <= curElement || index + 1 <= vote}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Rating>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal.Body>
|
||||||
|
<Modal.Footer>
|
||||||
|
<div className="flex gap-1 ml-auto">
|
||||||
|
<Button
|
||||||
|
disabled={isSending}
|
||||||
|
color={"red"}
|
||||||
|
onClick={() => {
|
||||||
|
setIsSending(true);
|
||||||
|
_setVote("delete", null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Убрать оценку
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={isSending || vote === null}
|
||||||
|
color={"blue"}
|
||||||
|
onClick={() => {
|
||||||
|
setIsSending(true);
|
||||||
|
_sendVote("delete", null);
|
||||||
|
setTimeout(() => _setVote("add", vote), 500);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Оценить
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -96,6 +96,7 @@ export const ReleasePage = (props: any) => {
|
||||||
{data.release.status.name.toLowerCase() != "анонс" && (
|
{data.release.status.name.toLowerCase() != "анонс" && (
|
||||||
<div className="[grid-column:2]">
|
<div className="[grid-column:2]">
|
||||||
<ReleaseInfoRating
|
<ReleaseInfoRating
|
||||||
|
release_id={props.id}
|
||||||
grade={data.release.grade}
|
grade={data.release.grade}
|
||||||
token={token}
|
token={token}
|
||||||
votes={{
|
votes={{
|
||||||
|
|
Loading…
Add table
Reference in a new issue