'use client' import type { AdminEventDetailData } from './admin-event-detail/types' import { useEffect, useMemo, useState } from 'react' import dynamic from 'next/dynamic' import { useParams, useRouter, useSearchParams } from 'next/navigation' import type { EventCategory, EventRevision } from '@/services/events' import type { City, Province } from '@/services/geography' import type { EventFaq, EventMedia } from '@/services/eventDetail' import type { EventManagementInsights } from '@/services/eventManagement' import { Tab } from '@/components/heroui/Tabs' import { addToast } from '@/lib/toast' import PageNavbar from '@/components/layouts/PageNavbar' import { DetailSkeleton } from '@/components/feedback/LoadingState' import AdminState from '@/components/feedback/AdminState' import AppTabs from '@/components/ui/AppTabs' import { APP_ROUTES } from '@/constants/routes' import { isEventEnded, isLiveEventStatus } from '@/features/events/eventEditPolicy' import { extractServerErrorDetail } from '@/services/errorHandler' import { approveEventAsAdmin, approveEventRevisionAsAdmin, cancelEventAsAdmin, completeEventAsAdmin, deleteEventAsAdmin, fetchAdminPendingEventRevision, fetchEventCategories, fetchAdminEventDetail, publishEventAsAdmin, rejectEventRevisionAsAdmin, updateEventAsAdmin, } from '@/services/events' import { fetchEventInsightsAsAdmin } from '@/services/eventManagement' import { fetchEventFaqs, fetchEventMedia } from '@/services/eventDetail' import { fetchAllCities, fetchProvinces } from '@/services/geography' import useAdminAction from '@/hooks/useAdminAction' import useAlertModal from '@/hooks/useAlertModal' import { texts } from '@/texts' import AdminEventDetailsSection from './admin-event-detail/AdminEventDetailsSection' import AdminEventLifecycleActions from './admin-event-detail/AdminEventLifecycleActions' import AdminEventOverviewTab from './admin-event-detail/AdminEventOverviewTab' import AdminEventPendingRevisionCard from './admin-event-detail/AdminEventPendingRevisionCard' import AdminEventRevisionRejectModal from './admin-event-detail/AdminEventRevisionRejectModal' import AdminEventSummaryCard from './admin-event-detail/AdminEventSummaryCard' const TabContentLoading = () => (
در حال بارگذاری اطلاعات…
) const AdminEventFinancialTab = dynamic(() => import('./admin-event-detail/AdminEventFinancialTab'), { loading: TabContentLoading, ssr: false, }) const EventDiscountsPanel = dynamic(() => import('@/features/events/detail/EventDiscountsPanel'), { loading: TabContentLoading, ssr: false, }) const AdminEventBookingsTab = dynamic(() => import('./admin-event-detail/AdminEventBookingsTab'), { loading: TabContentLoading, ssr: false, }) const AdminEventReviewsTab = dynamic(() => import('./admin-event-detail/AdminEventReviewsTab'), { loading: TabContentLoading, ssr: false, }) const EventGuestListAccessPanel = dynamic(() => import('@/components/events/EventGuestListAccessPanel'), { loading: TabContentLoading, ssr: false, }) const AdminEventRejectModal = dynamic(() => import('./admin-event-detail/AdminEventRejectModal'), { ssr: false }) const AdminEventCommissionModal = dynamic(() => import('./admin-event-detail/AdminEventCommissionModal'), { ssr: false }) const AdminEventDetail = () => { const params = useParams<{ id: string }>() const searchParams = useSearchParams() const router = useRouter() const eventId = params.id const initialTab = searchParams.get('tab') === 'discounts' ? 'discounts' : 'overview' const [event, setEvent] = useState(null) const [categories, setCategories] = useState([]) const [cities, setCities] = useState([]) const [provinces, setProvinces] = useState([]) const [media, setMedia] = useState([]) const [faqs, setFaqs] = useState([]) const [insights, setInsights] = useState(null) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) const [isTogglingDiscoverable, setIsTogglingDiscoverable] = useState(false) const [isRejectModalOpen, setIsRejectModalOpen] = useState(false) const [isCommissionModalOpen, setIsCommissionModalOpen] = useState(false) const [hasOpenedRejectModal, setHasOpenedRejectModal] = useState(false) const [hasOpenedCommissionModal, setHasOpenedCommissionModal] = useState(false) const [pendingRevision, setPendingRevision] = useState(null) const [isRevisionRejectModalOpen, setIsRevisionRejectModalOpen] = useState(false) const [hasOpenedRevisionRejectModal, setHasOpenedRevisionRejectModal] = useState(false) const { showAlert } = useAlertModal() const { pendingId, runAction } = useAdminAction() const mergeEvent = (updated: AdminEventDetailData) => { setEvent((prev) => (prev ? { ...prev, ...updated } : prev)) } const refreshInsights = () => { if (!eventId) return void fetchEventInsightsAsAdmin(eventId) .then(setInsights) .catch(() => undefined) } useEffect(() => { if (!eventId) return let cancelled = false const loadEvent = async () => { try { setIsLoading(true) setError(null) const [loadedEvent, loadedCategories, loadedCities, loadedProvinces, loadedMedia, loadedFaqs, loadedInsights] = await Promise.all([ fetchAdminEventDetail(eventId) as Promise, fetchEventCategories().catch(() => []), fetchAllCities().catch(() => []), fetchProvinces().catch(() => []), fetchEventMedia(eventId).catch(() => []), fetchEventFaqs(eventId).catch(() => []), fetchEventInsightsAsAdmin(eventId).catch(() => null), ]) if (cancelled) return setEvent(loadedEvent) setCategories(loadedCategories) setCities(loadedCities) setProvinces(loadedProvinces) setMedia(loadedMedia) setFaqs(loadedFaqs) setInsights(loadedInsights) } catch { if (!cancelled) { setError('رویداد یافت نشد') setEvent(null) } } finally { if (!cancelled) setIsLoading(false) } } void loadEvent() return () => { cancelled = true } }, [eventId]) useEffect(() => { if (!eventId || !event || !isLiveEventStatus(event.status)) { setPendingRevision(null) return } let cancelled = false const loadPendingRevision = async () => { try { const revision = await fetchAdminPendingEventRevision(eventId) if (!cancelled) setPendingRevision(revision) } catch { if (!cancelled) { setPendingRevision(null) addToast({ title: texts.events.revisionLoadFailed, color: 'danger' }) } } } void loadPendingRevision() return () => { cancelled = true } }, [event, eventId]) const categoryName = useMemo(() => categories.find((item) => item.id === event?.categoryId)?.name ?? '—', [categories, event?.categoryId]) const cityName = useMemo(() => cities.find((item) => item.id === event?.cityId)?.name ?? '—', [cities, event?.cityId]) const provinceName = useMemo(() => provinces.find((item) => item.id === event?.provinceId)?.name ?? '—', [event?.provinceId, provinces]) const handleApprove = () => { const isPendingReview = event?.status === 'pending_review' showAlert(isPendingReview ? 'این رویداد تأیید و منتشر شود؟' : 'این رویداد برای انتشار تأیید شود؟', () => runAction( 'approve', async () => { const updated = (await approveEventAsAdmin(eventId)) as AdminEventDetailData mergeEvent(updated) }, isPendingReview ? 'رویداد تأیید و منتشر شد' : 'رویداد برای انتشار تأیید شد' ) ) } // Bypasses the whole request/approve flow — publishes immediately // regardless of whether the host has requested publication or an admin // has approved yet. Kept as a separate action from "approve" on purpose. const handleForcePublish = () => { showAlert('این رویداد فوراً و بدون انتظار برای میزبان منتشر شود؟', () => runAction( 'force-publish', async () => { const updated = (await publishEventAsAdmin(eventId)) as AdminEventDetailData mergeEvent(updated) }, 'رویداد فورا منتشر شد' ) ) } const handleComplete = () => { showAlert('آیا این رویداد به پایان رسیده است؟', () => runAction( 'complete', async () => { const updated = (await completeEventAsAdmin(eventId)) as AdminEventDetailData mergeEvent(updated) refreshInsights() }, 'رویداد به‌عنوان پایان‌یافته علامت خورد' ) ) } const handleCancel = () => { showAlert( 'آیا از لغو این رویداد مطمئن هستید؟ همه‌ی رزروها لغو خواهند شد.', () => runAction( 'cancel', async () => { const updated = (await cancelEventAsAdmin(eventId)) as AdminEventDetailData mergeEvent(updated) refreshInsights() }, 'رویداد لغو شد' ), undefined, { dangerAccept: true } ) } const handleDelete = () => { showAlert( 'آیا از حذف این رویداد مطمئن هستید؟ فقط رویدادهای بدون رزرو فعال قابل حذف‌اند.', () => runAction( 'delete', async () => { await deleteEventAsAdmin(eventId) router.push(APP_ROUTES.MANAGE_EVENTS) }, 'رویداد حذف شد' ), undefined, { dangerAccept: true } ) } const handleApproveRevision = () => { if (!pendingRevision) return showAlert('این ویرایش تأیید و روی رویداد اعمال شود؟', () => runAction( 'approve-revision', async () => { const updated = (await approveEventRevisionAsAdmin(eventId, pendingRevision.id)) as AdminEventDetailData mergeEvent(updated) setPendingRevision(null) refreshInsights() }, texts.events.revisionApproveSuccess ) ) } const handleRejectRevision = (rejectionReason: string) => { if (!pendingRevision) return void runAction( 'reject-revision', async () => { await rejectEventRevisionAsAdmin(eventId, pendingRevision.id, rejectionReason) setPendingRevision(null) setIsRevisionRejectModalOpen(false) }, texts.events.revisionRejectSuccess ) } const handleToggleDiscoverable = (nextValue: boolean) => { if (!event || isTogglingDiscoverable) return const previous = event.settings.isDiscoverable showAlert(nextValue ? 'نمایش این رویداد در جستجوی عمومی فعال شود؟' : 'نمایش این رویداد در جستجوی عمومی غیرفعال شود؟', async () => { setEvent((prev) => (prev ? { ...prev, settings: { ...prev.settings, isDiscoverable: nextValue } } : prev)) setIsTogglingDiscoverable(true) try { await updateEventAsAdmin(eventId, { settings: { isDiscoverable: nextValue } }) addToast({ title: 'وضعیت جستجوی عمومی به‌روزرسانی شد', color: 'success' }) } catch (err) { setEvent((prev) => (prev ? { ...prev, settings: { ...prev.settings, isDiscoverable: previous } } : prev)) const detail = extractServerErrorDetail((err as { response?: { data?: unknown } })?.response?.data) addToast({ title: 'به‌روزرسانی ناموفق بود', description: detail ?? undefined, color: 'danger', }) } finally { setIsTogglingDiscoverable(false) } }) } return (
{ setHasOpenedRejectModal(true) setIsRejectModalOpen(true) }} /> ) : undefined } pageTitle={event?.title ?? 'جزئیات رویداد'} />
{isLoading && } {!isLoading && error && (
)} {!isLoading && event && ( <> {pendingRevision ? ( { setHasOpenedRevisionRejectModal(true) setIsRevisionRejectModalOpen(true) }} /> ) : null} {event.isFree ? null : ( )} {event.isFree ? null : ( )} { setHasOpenedCommissionModal(true) setIsCommissionModalOpen(true) }} /> )}
{hasOpenedRejectModal ? ( ) : null} {hasOpenedCommissionModal ? ( ) : null} {hasOpenedRevisionRejectModal ? ( ) : null}
) } export default AdminEventDetail