admin/features/events/management/EventReviewsPanel.tsx
2026-09-07 18:57:57 +03:30

133 lines
3.8 KiB
TypeScript

'use client'
import { useCallback, useEffect, useState } from 'react'
import Button from '@/components/formElements/Button'
import AdminState from '@/components/feedback/AdminState'
import ReviewCard from '@/components/reviews/ReviewCard'
import StatusChip from '@/components/ui/StatusChip'
import { getReviewStatus } from '@/constants/status'
import { EVENT_REVIEWS_PAGE_SIZE, LIST_ADMIN_EVENT_REVIEWS_PAGE, type AdminEventReview } from '@/services/reviews'
import { texts } from '@/texts'
interface EventReviewsPanelProps {
eventId: string
}
const authorDisplayName = (review: AdminEventReview) => {
const name = [review.user?.firstName, review.user?.lastName].filter(Boolean).join(' ').trim()
return name || texts.events.guest
}
/** Read-only admin reviews for one event (includes hidden/deleted). */
const EventReviewsPanel = ({ eventId }: EventReviewsPanelProps) => {
const [reviews, setReviews] = useState<AdminEventReview[]>([])
const [isLoading, setIsLoading] = useState(true)
const [isLoadingMore, setIsLoadingMore] = useState(false)
const [page, setPage] = useState(1)
const [totalItems, setTotalItems] = useState(0)
const fetchPage = useCallback(
async (nextPage: number, options?: { errorMode: 'silent' }) =>
LIST_ADMIN_EVENT_REVIEWS_PAGE(eventId, nextPage, EVENT_REVIEWS_PAGE_SIZE, options),
[eventId]
)
const load = useCallback(async () => {
setIsLoading(true)
const result = await fetchPage(1)
if (result.ok) {
setReviews(result.data.items)
setTotalItems(result.data.totalItemsCount)
setPage(1)
}
setIsLoading(false)
}, [fetchPage])
const loadMore = async () => {
if (isLoadingMore || reviews.length >= totalItems) return
setIsLoadingMore(true)
const nextPage = page + 1
const result = await fetchPage(nextPage, { errorMode: 'silent' })
if (result.ok) {
setReviews((current) => {
const seen = new Set(current.map((review) => review.id))
return [...current, ...result.data.items.filter((review) => !seen.has(review.id))]
})
}
setPage(nextPage)
setIsLoadingMore(false)
}
useEffect(() => {
void load()
}, [load])
if (isLoading) {
return <p className="mt-4 text-sm text-text-muted">{texts.reviews.loading}</p>
}
if (reviews.length === 0) {
return (
<div className="mt-4">
<AdminState
description={texts.reviews.hostEmptyDescription}
title={texts.reviews.hostEmptyTitle}
variant="empty"
/>
</div>
)
}
return (
<div className="mt-4 space-y-3">
<ul className="space-y-3">
{reviews.map((review) => {
const statusPresentation = getReviewStatus(review.status)
return (
<ReviewCard
key={review.id}
authorAvatarUrl={review.user?.avatarUrl}
authorName={authorDisplayName(review)}
authorUserId={review.user?.id}
body={review.body ?? texts.reviews.ratingOnly}
className="admin-surface space-y-2 p-4"
createdAt={review.createdAt}
footer={
<div className="mt-3">
<StatusChip
chipColor={statusPresentation.chipColor}
label={statusPresentation.label}
/>
</div>
}
hostReplyBody={review.hostReplyBody}
rating={review.rating}
tone="organizer"
/>
)
})}
</ul>
{reviews.length < totalItems ? (
<div className="flex justify-center">
<Button
isLoading={isLoadingMore}
variant="flat"
onClick={() => void loadMore()}
>
{texts.reviews.loadMore}
</Button>
</div>
) : null}
</div>
)
}
export default EventReviewsPanel