import React, { useState, useEffect, useMemo } from 'react' import { Ic } from '@/icons' import { Card, CardContent } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Switch } from '@/components/ui/switch' import { Skeleton } from '@/components/ui/skeleton' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { fetchTours, toggleTourVisibility } from './services/tour-api' import type { TourListItem } from './types' import { TourStatusBadge } from './components/TourStatusBadge' import { TourDetailDialog } from './components/TourDetailDialog' import { CreateTourDialog } from './components/CreateTourDialog' import TablePagination from '@/components/shared/TablePagination' import FilterSelect from '@/components/shared/FilterSelect' import SortDropdown from '@/components/shared/SortDropdown' import FilterBar from '@/components/shared/FilterBar' import { Shimmer } from '@/components/shared/shimmer_frontend' import { toFa } from '@/lib/utils' import { useTranslation } from '@/i18n' import { formatDateRangeByLanguage } from '@/lib/date-utils' export default function ToursPage() { const { language } = useTranslation() const [tours, setTours] = useState([]) const [isLoading, setIsLoading] = useState(true) const [isRefreshing, setIsRefreshing] = useState(false) const [error, setError] = useState(null) // فیلترها و مرتب‌سازی const [searchTerm, setSearchTerm] = useState('') const [statusFilter, setStatusFilter] = useState('ALL') const [tripStatusFilter, setTripStatusFilter] = useState('ALL') const [sortBy, setSortBy] = useState<'default' | 'started_at' | 'id' | 'price_asc' | 'price_desc'>('default') const [viewMode, setViewMode] = useState<'grid' | 'table'>('grid') // صفحه‌بندی (Pagination) const [currentPage, setCurrentPage] = useState(1) const [pageSize, setPageSize] = useState(20) // ۲۰ تور در هر صفحه // وضعیت مودال جزئیات و ترجمه‌های یکپارچه تور const [selectedTourId, setSelectedTourId] = useState(null) const [isDetailOpen, setIsDetailOpen] = useState(false) const [isCreateOpen, setIsCreateOpen] = useState(false) const [togglingId, setTogglingId] = useState(null) const handleToggleTourVisibility = async (tourId: number, currentVisibility?: boolean) => { const newVisibility = currentVisibility === false ? true : false setTogglingId(tourId) try { await toggleTourVisibility(tourId, newVisibility) setTours((prev) => prev.map((t) => (t.id === tourId ? { ...t, visibility: newVisibility } : t)) ) } catch (err: any) { alert(err?.message || 'خطا در تغییر وضعیت فعال/غیرفعال بودن تور') } finally { setTogglingId(null) } } const loadData = async (showRefresh = false) => { if (showRefresh) setIsRefreshing(true) else setIsLoading(true) setError(null) try { const res = await fetchTours({ limit: 100, language_code: language, }) setTours(res.results || []) } catch (err: any) { setError(err.message || 'خطا در برقراری ارتباط با وب‌سرویس تورها') } finally { setIsLoading(false) setIsRefreshing(false) } } useEffect(() => { loadData() }, [language]) // فیلتر، جستجو و مرتب‌سازی کلاینت const filteredTours = useMemo(() => { const list = tours.filter((tour) => { const matchSearch = !searchTerm.trim() || tour.title.toLowerCase().includes(searchTerm.toLowerCase()) || tour.slug.toLowerCase().includes(searchTerm.toLowerCase()) const matchStatus = statusFilter === 'ALL' || tour.status === statusFilter const matchTripStatus = tripStatusFilter === 'ALL' || tour.trip_status === tripStatusFilter return matchSearch && matchStatus && matchTripStatus }) const now = new Date() const nowMs = now.getTime() const todayStartMs = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime() return [...list].sort((a, b) => { if (sortBy === 'price_asc') return (parseFloat(a.price) || 0) - (parseFloat(b.price) || 0) if (sortBy === 'price_desc') return (parseFloat(b.price) || 0) - (parseFloat(a.price) || 0) if (sortBy === 'started_at') { const timeA = a.started_at ? new Date(a.started_at).getTime() : Infinity const timeB = b.started_at ? new Date(b.started_at).getTime() : Infinity return timeA - timeB } if (sortBy === 'id') return b.id - a.id // مرتب‌سازی پیش‌فرض: نزدیک‌ترین تور فعال یا در حال برگزاری // رتبه‌بندی اولویت: // ۱. در حال برگزاری (TRAVELING یا تطابق بازه تاریخی با زمان حال) // ۲. فعال و در دسترس (AVAILABLE و منقضی‌نشده، صعودی بر اساس نزدیک‌ترین تاریخ شروع) // ۳. سایر تورهای پیش‌رو (تکمیل ظرفیت SOLD_OUT و...) // ۴. تورهای پایان‌یافته (FINISHED یا تاریخ گذشته) const getTourBucket = (t: TourListItem) => { const startMs = t.started_at ? new Date(t.started_at).setHours(0, 0, 0, 0) : NaN const endMs = t.ended_at ? new Date(t.ended_at).setHours(23, 59, 59, 999) : startMs const isTraveling = t.trip_status === 'TRAVELING' || (!isNaN(startMs) && !isNaN(endMs) && nowMs >= startMs && nowMs <= endMs) if (isTraveling) { return { bucket: 1, date: !isNaN(startMs) ? startMs : 0 } } const isPast = t.trip_status === 'FINISHED' || (!isNaN(endMs) && endMs < todayStartMs) if (!isPast) { if (t.status === 'AVAILABLE') { return { bucket: 2, date: !isNaN(startMs) ? startMs : Infinity } } return { bucket: 3, date: !isNaN(startMs) ? startMs : Infinity } } return { bucket: 4, date: !isNaN(startMs) ? startMs : 0 } } const rankA = getTourBucket(a) const rankB = getTourBucket(b) if (rankA.bucket !== rankB.bucket) { return rankA.bucket - rankB.bucket } // در رتبه‌های ۱، ۲ و ۳، نزدیک‌ترین تاریخ حرکت در اولویت است if (rankA.bucket <= 3) { if (rankA.date !== rankB.date) { return rankA.date - rankB.date } } else { // در تورهای پایان‌یافته (رتبه ۴)، جدیدترین تاریخ به گذشته در ابتدا قرار می‌گیرد if (rankA.date !== rankB.date) { return rankB.date - rankA.date } } return b.id - a.id }) }, [tours, searchTerm, statusFilter, tripStatusFilter, sortBy]) // شمارش فیلترهای فعال const activeFiltersCount = [ statusFilter !== 'ALL', tripStatusFilter !== 'ALL', Boolean(searchTerm.trim()), ].filter(Boolean).length // بازنشانی صفحه به ۱ در صورت تغییر هرکدام از فیلترها، جستجو یا مرتب‌سازی useEffect(() => { setCurrentPage(1) }, [searchTerm, statusFilter, tripStatusFilter, sortBy]) // محاسبات صفحه‌بندی فهرست تورها const totalPages = Math.ceil(filteredTours.length / pageSize) || 1 const activePage = Math.min(currentPage, totalPages) const paginatedTours = useMemo(() => { const startIndex = (activePage - 1) * pageSize return filteredTours.slice(startIndex, startIndex + pageSize) }, [filteredTours, activePage, pageSize]) // شاخص‌های کلیدی آماری (KPI Metrics) const stats = useMemo(() => { const total = tours.length const active = tours.filter((t) => t.visibility !== false).length const inactive = tours.filter((t) => t.visibility === false).length const soldOut = tours.filter((t) => t.status === 'SOLD_OUT').length const traveling = tours.filter((t) => t.trip_status === 'TRAVELING').length const pending = tours.filter((t) => t.trip_status === 'PENDING').length return { total, active, inactive, soldOut, traveling, pending } }, [tours]) const handleOpenDetail = (id: number) => { setSelectedTourId(id) setIsDetailOpen(true) } // محاسبه طول مدت سفر به روز const calculateDays = (start: string, end: string) => { if (!start || !end) return '-' const s = new Date(start).getTime() const e = new Date(end).getTime() const diff = Math.ceil((e - s) / (1000 * 3600 * 24)) return diff > 0 ? `${diff} روز` : '۱ روز' } return (
{/* سربرگ مدیریت تورها */}

مدیریت تورها

{stats.total} تور ثبت‌شده

پایش وضعیت زنده فروش، برنامه‌های سفر، مسافران، اطلاعات قیمتی و ترجمه‌های چندزبانه

{/* تغییر نمای شبکه‌ای / جدولی */}
{/* دکمه ایجاد تور جدید */} {/* دکمه بروزرسانی زنده */}
{/* کارت‌های شاخص‌های کلیدی آماری (KPI Cards) */}

کل تورها

{isLoading ? ( ) : (

{toFa(stats.total)}

)}

تورهای فعال (اپ و وب)

{isLoading ? ( ) : (

{toFa(stats.active)}

)}

تکمیل ظرفیت

{isLoading ? ( ) : (

{toFa(stats.soldOut)}

)}

در حال اجرا

{isLoading ? ( ) : (

{toFa(stats.traveling)}

)}

در انتظار شروع

{isLoading ? ( ) : (

{toFa(stats.pending)}

)}
{/* نوار فیلتر و جستجو استاندارد */} 0 || Boolean(searchTerm)} activeFilterCount={activeFiltersCount} onResetFilters={() => { setSearchTerm('') setStatusFilter('ALL') setTripStatusFilter('ALL') setSortBy('default') }} endContent={ setSortBy(val as any)} options={[ { value: 'default', label: 'پیش‌فرض (نزدیک‌ترین فعال / در حال برگزاری)' }, { value: 'started_at', label: 'نزدیک‌ترین تاریخ حرکت' }, { value: 'id', label: 'جدیدترین (شناسه)' }, { value: 'price_asc', label: 'ارزان‌ترین تور' }, { value: 'price_desc', label: 'گران‌ترین تور' }, ]} size="sm" /> } > {/* وضعیت فروش */} {/* وضعیت سفر */} {/* وضعیت لودینگ استاندارد Shimmer */} {isLoading ? ( ) : error ? ( /* وضعیت خطا */

خطا در بارگذاری فهرست تورها

{error}

) : filteredTours.length === 0 ? ( /* وضعیت خالی (Empty State) */

توری یافت نشد

هیچ توری با فیلترها و عبارت جستجوی انتخابی شما همخوانی ندارد.

) : viewMode === 'grid' ? ( /* نمای شبکه‌ای (Cards Grid View) */
{paginatedTours.map((tour) => { const imgUrl = tour.image?.image_url?.md || tour.image?.image_url?.medium || tour.image?.image_url?.lg || tour.image?.image_url?.large || tour.image?.image_url?.original || tour.image?.image_url?.sm || tour.image?.image_url?.small || '/placeholder.jpg' return (
handleOpenDetail(tour.id)} className="group flex flex-col justify-between overflow-hidden rounded-2xl border border-border-soft bg-card/40 transition hover:border-primary/40 hover:bg-card/70 hover:shadow-md cursor-pointer" >
{/* تصویر کاور تور و وضعیت */}
{tour.title} { ;(e.target as HTMLImageElement).src = 'data:image/svg+xml;utf8,Aqila Tour' }} /> {/* وضعیت تور و تاگل تعاملی نمایش */}
e.stopPropagation()} >
handleToggleTourVisibility(tour.id, tour.visibility !== false)} className="scale-75 origin-right" title="تغییر وضعیت فعال/غیرفعال بودن تور در اپ و وب" /> {tour.visibility !== false ? 'فعال' : 'مخفی'}
{/* بدنه کارت */}
#{tour.id} {calculateDays(tour.started_at, tour.ended_at)}

{tour.title}

{/* تاریخ برگزاری */}
{formatDateRangeByLanguage(tour.started_at, tour.ended_at, language as any)}
{/* جدول ۳ ردیف قیمت تور بر اساس رده سنی */}
بزرگسال (۷+ سال): ${parseFloat(tour.price || '0').toLocaleString()}
کودک (۲ تا ۷ سال): ${parseFloat(tour.price_child || '0').toLocaleString()}
نوزاد (۰ تا ۲ سال): ${parseFloat(tour.price_infant || '0').toLocaleString()}
{/* فوتر کارت و دکمه جزئیات */}
) })}
) : ( /* نمای جدول (Data Table View) */
شناسه عنوان تور تاریخ برگزاری نرخ‌ها (بزرگسال / کودک / نوزاد) وضعیت نمایش وضعیت سفر عملیات {paginatedTours.map((tour) => ( handleOpenDetail(tour.id)} > #{tour.id}
{tour.title}
{tour.slug}
{formatDateRangeByLanguage(tour.started_at, tour.ended_at, language as any)}
${parseFloat(tour.price || '0').toLocaleString()}
کودک: ${parseFloat(tour.price_child || '0').toLocaleString()} | نوزاد: ${parseFloat(tour.price_infant || '0').toLocaleString()}
e.stopPropagation()}>
handleToggleTourVisibility(tour.id, tour.visibility !== false)} title="تغییر وضعیت فعال/غیرفعال بودن تور در اپ و وب" /> {tour.visibility !== false ? 'روشن (فعال)' : 'خاموش (مخفی)'}
))}
)} {/* کنترل صفحه‌بندی فهرست تورها (Pagination Footer) */} {!isLoading && !error && filteredTours.length > 0 && (
{/* نمایش خلاصه تعداد آیتم‌ها و انتخاب تعداد در هر صفحه */}
نمایش{' '} {toFa((activePage - 1) * pageSize + 1)} {' '} تا{' '} {toFa(Math.min(activePage * pageSize, filteredTours.length))} {' '} از{' '} {toFa(filteredTours.length)} {' '} تور
در هر صفحه: { setPageSize(Number(val)) setCurrentPage(1) }} options={[ { value: '10', label: '۱۰' }, { value: '20', label: '۲۰' }, { value: '50', label: '۵۰' }, { value: '100', label: '۱۰۰' }, ]} size="sm" className="w-20" triggerClassName="h-8 py-1 px-2.5 text-xs font-bold" />
{/* کامپوننت صفحه‌بندی استاندارد پنل */} { setCurrentPage(p) window.scrollTo({ top: 0, behavior: 'smooth' }) }} />
)} {/* مودال پاپ‌آپ جامع جزئیات و ترجمه‌های تور */} setIsDetailOpen(false)} onUpdated={() => loadData()} /> {/* مودال ایجاد تور جدید به همراه ترجمه‌های ۵ زبانه */} loadData(true)} />
) }