review detail sheet on diary/profile/community, wrapup period presets
This commit is contained in:
@@ -7,7 +7,7 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/com
|
||||
import { timeAgo } from "@/lib/date"
|
||||
import type { SocialReviewDto } from "@/features/movies"
|
||||
|
||||
export function CommunityReviews({ reviews }: { reviews: { items: SocialReviewDto[] } }) {
|
||||
export function CommunityReviews({ reviews, onShowDetail }: { reviews: { items: SocialReviewDto[] }; onShowDetail?: (review: SocialReviewDto) => void }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
@@ -36,7 +36,13 @@ export function CommunityReviews({ reviews }: { reviews: { items: SocialReviewDt
|
||||
</CardHeader>
|
||||
{r.comment && (
|
||||
<CardContent>
|
||||
<p className="text-xs text-muted-foreground">{r.comment}</p>
|
||||
<p
|
||||
className="text-xs text-muted-foreground"
|
||||
role={onShowDetail ? "button" : undefined}
|
||||
tabIndex={onShowDetail ? 0 : undefined}
|
||||
onClick={onShowDetail ? () => onShowDetail(r) : undefined}
|
||||
onKeyDown={onShowDetail ? (e) => e.key === "Enter" && onShowDetail(r) : undefined}
|
||||
>{r.comment}</p>
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
@@ -11,9 +11,10 @@ type MovieCardProps = {
|
||||
subtitle?: React.ReactNode
|
||||
variant?: "compact" | "full"
|
||||
action?: React.ReactNode
|
||||
onShowDetail?: () => void
|
||||
}
|
||||
|
||||
export function MovieCard({ movie, rating, comment, subtitle, variant = "full", action }: MovieCardProps) {
|
||||
export function MovieCard({ movie, rating, comment, subtitle, variant = "full", action, onShowDetail }: MovieCardProps) {
|
||||
if (variant === "compact") {
|
||||
return (
|
||||
<Link to="/movies/$id" params={{ id: movie.id }} className="glass flex items-center gap-3 rounded-xl px-3 py-2.5 transition-colors active:bg-muted/50">
|
||||
@@ -23,7 +24,15 @@ export function MovieCard({ movie, rating, comment, subtitle, variant = "full",
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-semibold">{movie.title}</p>
|
||||
{subtitle && <p className="text-xs text-muted-foreground">{subtitle}</p>}
|
||||
{comment && <p className="truncate text-xs text-muted-foreground/70">{comment}</p>}
|
||||
{comment && (
|
||||
<p
|
||||
className="truncate text-xs text-muted-foreground/70"
|
||||
role={onShowDetail ? "button" : undefined}
|
||||
tabIndex={onShowDetail ? 0 : undefined}
|
||||
onClick={onShowDetail ? (e) => { e.preventDefault(); onShowDetail() } : undefined}
|
||||
onKeyDown={onShowDetail ? (e) => e.key === "Enter" && onShowDetail() : undefined}
|
||||
>{comment}</p>
|
||||
)}
|
||||
</div>
|
||||
{rating != null && <StarDisplay rating={rating} size="xs" />}
|
||||
</Link>
|
||||
@@ -41,7 +50,15 @@ export function MovieCard({ movie, rating, comment, subtitle, variant = "full",
|
||||
<p className="font-semibold">{movie.title}</p>
|
||||
<p className="text-xs text-muted-foreground">{movie.release_year}{movie.director && ` · ${movie.director}`}</p>
|
||||
{rating != null && <div className="mt-1"><StarDisplay rating={rating} /></div>}
|
||||
{comment && <p className="mt-1 line-clamp-2 text-xs text-muted-foreground">{comment}</p>}
|
||||
{comment && (
|
||||
<p
|
||||
className="mt-1 line-clamp-2 text-xs text-muted-foreground"
|
||||
role={onShowDetail ? "button" : undefined}
|
||||
tabIndex={onShowDetail ? 0 : undefined}
|
||||
onClick={onShowDetail ? (e) => { e.preventDefault(); onShowDetail() } : undefined}
|
||||
onKeyDown={onShowDetail ? (e) => e.key === "Enter" && onShowDetail() : undefined}
|
||||
>{comment}</p>
|
||||
)}
|
||||
</div>
|
||||
{action && <div className="flex items-center" onClick={(e) => e.preventDefault()}>{action}</div>}
|
||||
</CardContent>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Link } from "@tanstack/react-router"
|
||||
import { useCallback } from "react"
|
||||
import { useCallback, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Bar, BarChart, XAxis, YAxis } from "recharts"
|
||||
import { Globe, Search, User } from "lucide-react"
|
||||
@@ -16,6 +16,8 @@ import { VirtualList } from "@/components/virtual-list"
|
||||
import { useInfiniteDiary } from "@/features/diary"
|
||||
import { TimeAgo } from "@/components/time-ago"
|
||||
import { WATCH_MEDIUMS } from "@/lib/watch-mediums"
|
||||
import { ReviewDetailSheet } from "@/components/review-detail-sheet"
|
||||
import type { DiaryEntryDto } from "@/lib/api/common"
|
||||
import type { UserProfileResponse } from "@/features/users"
|
||||
|
||||
type ProfileViewProps = {
|
||||
@@ -154,27 +156,39 @@ function DiaryTab({ sortBy, userId, search }: { sortBy: string; userId?: string;
|
||||
)
|
||||
: items
|
||||
const loadMore = useCallback(() => fetchNextPage(), [fetchNextPage])
|
||||
const [detailEntry, setDetailEntry] = useState<DiaryEntryDto | null>(null)
|
||||
|
||||
if (isPending) return <Skeleton className="h-40 w-full rounded-xl" />
|
||||
if (!filtered.length) return <EmptyState icon={User} title={t("profile.noEntries")} />
|
||||
|
||||
return (
|
||||
<VirtualList
|
||||
items={filtered}
|
||||
estimateSize={52}
|
||||
hasMore={!!hasNextPage}
|
||||
isFetching={isFetchingNextPage}
|
||||
onLoadMore={loadMore}
|
||||
renderItem={(e) => (
|
||||
<MovieCard
|
||||
movie={e.movie}
|
||||
rating={e.review.rating}
|
||||
comment={e.review.comment}
|
||||
subtitle={<><TimeAgo date={e.review.watched_at} /></>}
|
||||
variant="compact"
|
||||
<>
|
||||
<VirtualList
|
||||
items={filtered}
|
||||
estimateSize={52}
|
||||
hasMore={!!hasNextPage}
|
||||
isFetching={isFetchingNextPage}
|
||||
onLoadMore={loadMore}
|
||||
renderItem={(e) => (
|
||||
<MovieCard
|
||||
movie={e.movie}
|
||||
rating={e.review.rating}
|
||||
comment={e.review.comment}
|
||||
subtitle={<><TimeAgo date={e.review.watched_at} /></>}
|
||||
variant="compact"
|
||||
onShowDetail={e.review.comment ? () => setDetailEntry(e) : undefined}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{detailEntry && (
|
||||
<ReviewDetailSheet
|
||||
open={!!detailEntry}
|
||||
onOpenChange={(open) => !open && setDetailEntry(null)}
|
||||
movie={detailEntry.movie}
|
||||
review={detailEntry.review}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user