feat: add user vote change for a release in a rating widget

This commit is contained in:
Kentai Radiquum 2024-08-07 16:07:03 +05:00
parent 8f6319ab0d
commit c947f36289
Signed by: Radiquum
GPG key ID: 858E8EE696525EED
3 changed files with 215 additions and 81 deletions

View file

@ -66,9 +66,9 @@
- [X] Добавление \ Удаление аниме в\из списков закладок и избранных - [X] Добавление \ Удаление аниме в\из списков закладок и избранных
- [X] Связанные релизы - [X] Связанные релизы
- [X] Просмотр страницы всех связанных релизов - [X] Просмотр страницы всех связанных релизов
- [ ] Оценка тайтла - [X] Оценка тайтла
- [X] Просмотр оценки - [X] Просмотр оценки
- [ ] Оценивание и изменение своей оценки (как Modal) - [X] Оценивание и изменение своей оценки (как Modal)
## Стиль ## Стиль

View file

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

View file

@ -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={{