implemented ratings, fixes

This commit is contained in:
lnn0q 2025-06-01 17:54:57 +03:00
parent 4801cf3a13
commit d1da980501
31 changed files with 775 additions and 236 deletions

View file

@ -0,0 +1,177 @@
import { gql, useMutation, useQuery } from '@apollo/client';
import { BookChapterCard } from '@app/client/components/BookChapterCard';
import { BookOverviewTabs } from '@app/client/components/BookOverviewTabs';
import { PaginatedListSkeleton } from '@app/client/components/PaginatedListSkeleton';
import { Pagination } from '@app/client/components/Pagination';
import { RatingPanel } from '@app/client/components/RatingPanel';
import { Button } from '@app/client/components/ui/button';
import { bookChapterFragment } from '@app/client/fragments';
import { useIdentity } from '@app/client/hooks/identity';
import { usePaginatedQuery } from '@app/client/hooks/usePaginatedQuery';
import { Box, Skeleton, Stack, Text } from '@chakra-ui/react';
import { BookChapterListQuery, BookOverviewRatingQuery } from '@generated/types';
import { createFileRoute, Link } from '@tanstack/react-router';
import { useState } from 'react';
export const Route = createFileRoute('/work/book/$bookId/overview/contents')({
component: RouteComponent
});
function RouteComponent() {
const { bookId: id } = Route.useParams();
const identity = useIdentity();
const { data } = useQuery<BookOverviewRatingQuery>(bookRatingQuery, {
variables: { id }
});
const [ratingUpdate] = useMutation<BookOverviewRatingQuery>(bookRatingMutation, {
refetchQueries: [bookRatingQuery]
});
return (
<>
<BookOverviewTabs bookId={id} currentTab="contents" />
{data != null ? (
<>
<BookChapterList
id={data.bookOne.id}
isAuthor={identity?.data.id === data.bookOne.author.id}
/>
{identity != null &&
identity?.data.id !== data.bookOne.author.id &&
data.bookOne.chaptersTotal > 0 && (
<RatingPanel
personalRating={data.bookOne.personalRating}
onRatingSet={async ratingType => {
await ratingUpdate({
variables: {
input: {
bookId: data.bookOne.id,
ratingType
}
}
});
}}
/>
)}
</>
) : (
<Skeleton height="200px" />
)}
</>
);
}
function BookChapterList({ id, isAuthor }: { readonly id: string; readonly isAuthor: boolean }) {
const [chaptersListPage, setChaptersListPage] = useState<number>(1);
const [chaptersListPageSize, setChaptersListPageSize] = useState<number>(10);
const startIndex = (chaptersListPage - 1) * chaptersListPageSize;
const { data } = usePaginatedQuery<BookChapterListQuery>({
query: bookChapterListQuery,
input: {
bookId: id,
filter: {
offset: startIndex,
size: chaptersListPageSize
}
}
});
return (
<Stack>
{data != null ? (
<>
<Stack>
{data.bookChapterMany.items.length > 0 ? (
<>
{data.bookChapterMany.items.map(item => (
<Stack key={`bookChapterList-${item.id}`}>
<BookChapterCard
bookId={id}
chapterId={item.id}
title={item.title}
isAuthor={isAuthor}
/>
</Stack>
))}
</>
) : (
<Stack justifyContent="center" alignItems="center" pt={8}>
<Text>No chapters here yet!</Text>
{isAuthor && (
<Link to={`/work/book/${id}/chapter/new`}>
<Button variant="outline" size="sm">
Create new chapter
</Button>
</Link>
)}
</Stack>
)}
</Stack>
{data.bookChapterMany.total > chaptersListPageSize && (
<Stack justifyContent="center" alignItems="center">
<Pagination
total={data.bookChapterMany.total}
page={chaptersListPage}
setPage={setChaptersListPage}
pageSize={chaptersListPageSize}
setPageSize={setChaptersListPageSize}
/>
</Stack>
)}
<Stack direction="row">
<>
{isAuthor && data.bookChapterMany.items.length !== 0 && (
<Box>
<Link to={`/work/book/${id}/chapter/new`}>
<Button size="sm">Create new chapter</Button>
</Link>
</Box>
)}
</>
</Stack>
</>
) : (
<PaginatedListSkeleton disableSearch />
)}
</Stack>
);
}
export const bookChapterListQuery = gql`
query BookChapterList($input: BookChapterManyInput!) {
bookChapterMany(input: $input) {
items {
...BookChapter
}
total
}
}
${bookChapterFragment}
`;
const bookRatingQuery = gql`
query BookOverviewRating($id: ID!) {
bookOne(id: $id) {
id
ratingTotal
personalRating
chaptersTotal
author {
id
}
}
}
`;
const bookRatingMutation = gql`
mutation BookOverviewRatingUpdate($input: RatingSetInput!) {
ratingSet(input: $input)
}
`;

View file

@ -0,0 +1,119 @@
import { gql } from '@apollo/client';
import { BookOverviewTabs } from '@app/client/components/BookOverviewTabs';
import { Pagination } from '@app/client/components/Pagination';
import { reviewFragment } from '@app/client/fragments';
import { useIdentity } from '@app/client/hooks/identity';
import { usePaginatedQuery } from '@app/client/hooks/usePaginatedQuery';
import { useToastMutation } from '@app/client/hooks/useToastMutation';
import { Skeleton, Stack, Text } from '@chakra-ui/react';
import { BookReviewCreateMutation, BookReviewsQuery } from '@generated/types';
import { createFileRoute } from '@tanstack/react-router';
import { useState } from 'react';
import { bookQuery } from '../overview';
import { ReviewInput } from '@app/client/components/ReviewInput';
import { ReviewCard } from '@app/client/components/ReviewCard';
export const Route = createFileRoute('/work/book/$bookId/overview/reviews')({
component: RouteComponent
});
function RouteComponent() {
const { bookId: id } = Route.useParams();
const identity = useIdentity();
const [reviewsPage, setChaptersListPage] = useState<number>(1);
const [reviewsPageSize, setChaptersListPageSize] = useState<number>(10);
const startIndex = (reviewsPage - 1) * reviewsPageSize;
const { data } = usePaginatedQuery<BookReviewsQuery>({
query: bookReviewsQuery,
input: {
bookId: id,
filter: {
offset: startIndex,
size: reviewsPageSize
}
}
});
const { update: reviewCreate } = useToastMutation<BookReviewCreateMutation>({
mutation: bookReviewCreateMutation,
successMsg: 'Review posted!',
refetchQueries: [bookReviewsQuery, bookQuery]
});
if (data == null) return <Skeleton />;
return (
<>
<BookOverviewTabs bookId={id} currentTab="reviews" />
{identity != null && reviewsPage === 1 && (
<ReviewInput
username={identity?.data.user.username}
onReviewSubmit={async text => {
await reviewCreate({
variables: {
input: {
bookId: id,
text
}
}
});
}}
/>
)}
{data.reviewMany.items.length === 0 ? (
<Stack justifyContent="center" alignItems="center" pt={8}>
<Text>No reviews here yet!</Text>
</Stack>
) : (
data.reviewMany.items.map(item => (
<ReviewCard
key={`bookReview#${item.id}`}
review={{
id: item.id,
username: item.author.username,
text: item.text,
createdAt: item.createdAt
}}
/>
))
)}
{data.reviewMany.total > reviewsPageSize && (
<Stack justifyContent="center" alignItems="center">
<Pagination
total={data.reviewMany.total}
page={reviewsPage}
setPage={setChaptersListPage}
pageSize={reviewsPageSize}
setPageSize={setChaptersListPageSize}
/>
</Stack>
)}
</>
);
}
const bookReviewsQuery = gql`
query BookReviews($input: ReviewManyInput!) {
reviewMany(input: $input) {
items {
...Review
}
total
}
}
${reviewFragment}
`;
const bookReviewCreateMutation = gql`
mutation BookReviewCreate($input: ReviewCreateInput!) {
reviewCreate(input: $input) {
...Review
}
}
${reviewFragment}
`;