You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1041 lines
54 KiB
1041 lines
54 KiB
import React, { useState, useEffect, useMemo } from 'react'
|
|
import { useParams, useNavigate, Link, Navigate } from 'react-router-dom'
|
|
import { Ic, IconName } from '@/icons'
|
|
import { Card, CardContent } from '@/components/ui/card'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { Shimmer } from '@/components/shared/shimmer_frontend'
|
|
import { toFa } from '@/lib/utils'
|
|
import { SimcardServiceView } from './components/SimcardServiceView'
|
|
import { ZayerGuideServiceView } from './components/ZayerGuideServiceView'
|
|
import { DuasServiceView } from './components/DuasServiceView'
|
|
import { CategoriesServiceView } from './components/CategoriesServiceView'
|
|
import { CitiesServiceView } from './components/CitiesServiceView'
|
|
import { FaqServiceView } from './components/FaqServiceView'
|
|
import { StoreServiceView } from './components/StoreServiceView'
|
|
|
|
// تعریف ساختار دادههای سرویسها
|
|
export interface ServiceMeta {
|
|
key: string
|
|
title: string
|
|
titleEn: string
|
|
icon: IconName
|
|
color: string
|
|
bgColor: string
|
|
borderColor: string
|
|
description: string
|
|
endpoint: string
|
|
statsLabel: string
|
|
defaultCount: number
|
|
badge: string
|
|
}
|
|
|
|
export const SERVICES_LIST: ServiceMeta[] = [
|
|
{
|
|
key: 'simcard',
|
|
title: 'سیمکارت',
|
|
titleEn: 'Simcard',
|
|
icon: 'simcard',
|
|
color: 'text-primary',
|
|
bgColor: 'bg-primary/10',
|
|
borderColor: 'border-primary/20',
|
|
description: 'مدیریت بستههای اینترنت، سیمکارتهای بینالمللی و سفارشهای زائرین (ایران، عراق، عربستان سعودی)',
|
|
endpoint: '/store/simcards/',
|
|
statsLabel: 'بستههای فعال',
|
|
defaultCount: 9,
|
|
badge: 'ارتباطات و دیتا',
|
|
},
|
|
{
|
|
key: 'zayer-guide',
|
|
title: 'راهنمای زائر',
|
|
titleEn: 'Zayer Guide',
|
|
icon: 'guide',
|
|
color: 'text-primary',
|
|
bgColor: 'bg-primary/10',
|
|
borderColor: 'border-primary/20',
|
|
description: 'بانک جامع اماکن متبرکه، راهنمای اماکن تاریخی، بیمارستانها، مواکب و موقعیت مکانی شهرها',
|
|
endpoint: '/cityguide/',
|
|
statsLabel: 'مکان ثبتشده',
|
|
defaultCount: 42,
|
|
badge: 'اماکن و نقشهها',
|
|
},
|
|
{
|
|
key: 'exchange',
|
|
title: 'صرافی آنلاین و تشریفات',
|
|
titleEn: 'Currency Exchange',
|
|
icon: 'exchange',
|
|
color: 'text-primary',
|
|
bgColor: 'bg-primary/10',
|
|
borderColor: 'border-primary/20',
|
|
description: 'نرخ لحظهای ارزهای زیارتی (دینار، ریال، دلار، لیر)، تبدیل آنلاین و سفارش تحویل وجه نقد در مقصد',
|
|
endpoint: '/tasrif/orders/',
|
|
statsLabel: 'سفارش تبدیل ارز',
|
|
defaultCount: 19,
|
|
badge: 'مالی و ارزی',
|
|
},
|
|
{
|
|
key: 'cities',
|
|
title: 'شهرهای زیارتی',
|
|
titleEn: 'Holy Cities',
|
|
icon: 'map',
|
|
color: 'text-primary',
|
|
bgColor: 'bg-primary/10',
|
|
borderColor: 'border-primary/20',
|
|
description: 'فهرست استاتیک و رسمی شهرهای زیارتی اپلیکیشن عقیله به همراه شناسههای ثابت دیتابیس و دستهبندیهای اعمال',
|
|
endpoint: '/trip-etiquette/cities/',
|
|
statsLabel: 'شهر زیارتی مرجع',
|
|
defaultCount: 10,
|
|
badge: 'استاتیک و مرجع',
|
|
},
|
|
{
|
|
key: 'duas',
|
|
title: 'ادعیه و زیارات',
|
|
titleEn: 'Duas & Ziyarat',
|
|
icon: 'book',
|
|
color: 'text-primary',
|
|
bgColor: 'bg-primary/10',
|
|
borderColor: 'border-primary/20',
|
|
description: 'فهرست مستقیم ادعیه، تخصیص چندگانه به حرمها، مدیریت متن عربی با اعراب و ترجمههای 5 زبانه',
|
|
endpoint: '/trip-etiquette/duas/',
|
|
statsLabel: 'دعا و زیارت ثبتشده',
|
|
defaultCount: 4,
|
|
badge: 'فرهنگی و معنوی',
|
|
},
|
|
{
|
|
key: 'categories',
|
|
title: 'دستهبندیهای ادعیه',
|
|
titleEn: 'Dua Categories',
|
|
icon: 'folder',
|
|
color: 'text-primary',
|
|
bgColor: 'bg-primary/10',
|
|
borderColor: 'border-primary/20',
|
|
description: 'تعریف و مدیریت پوشهها و دستهبندیهای ادعیه (مانند اعمال، آداب، زیارات) به تفکیک شهرها و تخصیص مستقیم ادعیه',
|
|
endpoint: '/trip-etiquette/categories/',
|
|
statsLabel: 'دستهبندی فعال',
|
|
defaultCount: 6,
|
|
badge: 'شهرها و پوشهها',
|
|
},
|
|
{
|
|
key: 'store',
|
|
title: 'فروشگاه معصومه',
|
|
titleEn: 'Massoumah Store',
|
|
icon: 'store',
|
|
color: 'text-primary',
|
|
bgColor: 'bg-primary/10',
|
|
borderColor: 'border-primary/20',
|
|
description: 'سوغات متبرک، ملزومات سفر اربعین و عتبات، کتب ادعیه، چفیه، سجاده و محصولات فرهنگی',
|
|
endpoint: '/store/products/',
|
|
statsLabel: 'محصول در انبار',
|
|
defaultCount: 35,
|
|
badge: 'فروشگاه و سوغات',
|
|
},
|
|
{
|
|
key: 'cost-estimate',
|
|
title: 'تخمین هزینه سفر',
|
|
titleEn: 'Cost Estimate',
|
|
icon: 'calculator',
|
|
color: 'text-primary',
|
|
bgColor: 'bg-primary/10',
|
|
borderColor: 'border-primary/20',
|
|
description: 'سامانه هوشمند برآورد هزینههای اقامت، پرواز، ترانسفر و خوراک برای سفرهای انفرادی و گروهی',
|
|
endpoint: '/cost-estimates/',
|
|
statsLabel: 'آیتم محاسباتی',
|
|
defaultCount: 14,
|
|
badge: 'محاسبات و برآورد',
|
|
},
|
|
{
|
|
key: 'faq',
|
|
title: 'سوالات متداول',
|
|
titleEn: 'FAQ',
|
|
icon: 'helpCircle',
|
|
color: 'text-primary',
|
|
bgColor: 'bg-primary/10',
|
|
borderColor: 'border-primary/20',
|
|
description: 'مدیریت جامع دستهبندیهای موضوعی، پرسش و پاسخها، پشتیبانی کامل 5 زبانه و فعال/غیرفعالسازی نمایش در اپلیکیشن',
|
|
endpoint: '/home/faq/categories/',
|
|
statsLabel: 'دستهبندی فعال',
|
|
defaultCount: 6,
|
|
badge: 'پشتیبانی و راهنما',
|
|
},
|
|
{
|
|
key: 'consultant',
|
|
title: 'پشتیبانی',
|
|
titleEn: 'Support',
|
|
icon: 'headset',
|
|
color: 'text-primary',
|
|
bgColor: 'bg-primary/10',
|
|
borderColor: 'border-primary/20',
|
|
description: 'شبکه کارشناسان تخصصی پزشکی سفر، احکام، امور حقوقی، گمشدگان و پشتیبانی 24 ساعته زائرین',
|
|
endpoint: '/consultants/',
|
|
statsLabel: 'کارشناس فعال',
|
|
defaultCount: 12,
|
|
badge: 'پشتیبانی زنده',
|
|
},
|
|
]
|
|
|
|
export default function ServicesPage() {
|
|
const { serviceKey, subKey } = useParams<{ serviceKey?: string; subKey?: string }>()
|
|
|
|
// اگر مسیر /services/store بدون زیرمجموعه فراخوانی شد، به صفحه محصولات هدایت شود
|
|
if (serviceKey === 'store' && !subKey) {
|
|
return <Navigate to="/services/store/products" replace />
|
|
}
|
|
|
|
const currentService = useMemo(() => {
|
|
if (!serviceKey) return null
|
|
const base = SERVICES_LIST.find((s) => s.key === serviceKey)
|
|
if (!base) return null
|
|
|
|
if (serviceKey === 'store' && subKey === 'categories') {
|
|
return {
|
|
...base,
|
|
title: 'دستهبندیهای فروشگاه',
|
|
titleEn: 'Store Categories',
|
|
icon: 'folder' as const,
|
|
description: 'مدیریت و ساماندهی دستهبندیهای فعال فروشگاه، تعریف دستهبندی جدید و کنترل وضعیت نمایش در اپلیکیشن',
|
|
}
|
|
}
|
|
if (serviceKey === 'store' && subKey === 'products') {
|
|
return {
|
|
...base,
|
|
title: 'محصولات فروشگاه معصومه',
|
|
titleEn: 'Store Products',
|
|
icon: 'store' as const,
|
|
description: 'سوغات متبرک، ملزومات سفر اربعین و عتبات، کتب ادعیه، چفیه، سجاده و محصولات فرهنگی',
|
|
}
|
|
}
|
|
|
|
return base
|
|
}, [serviceKey, subKey])
|
|
|
|
if (!serviceKey || !currentService) {
|
|
return <Navigate to="/services/simcard" replace />
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6 pt-2 pb-14">
|
|
{/* سربرگ بخش خدمات (برای شهرهای زیارتی به صورت یکپارچه داخل کامپوننت با دکمه لود مجدد مدیریت میشود) */}
|
|
{serviceKey !== 'cities' && (
|
|
<div className="flex flex-col gap-4 rounded-2xl border border-border-soft bg-card/40 p-5 shadow-xs sm:flex-row sm:items-center sm:justify-between backdrop-blur-xs">
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-2.5">
|
|
<div className="flex size-9 items-center justify-center rounded-xl bg-primary/10 text-primary">
|
|
<Ic name={currentService.icon} className="size-5" />
|
|
</div>
|
|
<h1 className="text-xl font-black text-foreground">
|
|
{currentService.title}
|
|
</h1>
|
|
<Badge
|
|
variant="outline"
|
|
className="border-primary/30 bg-primary/10 text-primary font-bold font-mono text-xs"
|
|
>
|
|
{currentService.titleEn}
|
|
</Badge>
|
|
</div>
|
|
<p className="text-xs text-grey-3">
|
|
{currentService.description}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 shrink-0">
|
|
<Badge
|
|
variant="outline"
|
|
className="border-border-soft bg-card/70 text-grey-2 text-xs py-1.5 px-3 rounded-xl font-medium gap-1.5 shadow-2xs"
|
|
>
|
|
<span className="size-2 rounded-full bg-emerald-500 animate-pulse" />
|
|
<span>{currentService.badge}</span>
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* رندرینگ محتوای اختصاصی سرویس مستقل */}
|
|
<ServiceDetailRenderer service={currentService} subKey={subKey} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// رندرینگ محتوای اختصاصی هر سرویس بر اساس کلید آن
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
function ServiceDetailRenderer({
|
|
service,
|
|
subKey,
|
|
}: {
|
|
service: ServiceMeta
|
|
subKey?: string
|
|
}) {
|
|
switch (service.key) {
|
|
case 'simcard':
|
|
return <SimcardServiceView service={service} />
|
|
case 'zayer-guide':
|
|
return <ZayerGuideServiceView service={service} />
|
|
case 'exchange':
|
|
return <ExchangeServiceView service={service} />
|
|
case 'cities':
|
|
return <CitiesServiceView />
|
|
case 'duas':
|
|
return <DuasServiceView />
|
|
case 'categories':
|
|
return <CategoriesServiceView />
|
|
case 'shrines':
|
|
return <Navigate to="/services/duas" replace />
|
|
case 'trip-etiquette':
|
|
return <DuasServiceView />
|
|
case 'consultant':
|
|
return <ConsultantServiceView service={service} />
|
|
case 'faq':
|
|
return <FaqServiceView />
|
|
case 'store':
|
|
return (
|
|
<StoreServiceView
|
|
service={service}
|
|
initialTab={subKey === 'categories' ? 'categories' : 'products'}
|
|
/>
|
|
)
|
|
case 'cost-estimate':
|
|
return <CostEstimateServiceView service={service} />
|
|
default:
|
|
return <div className="p-8 text-center text-xs text-grey-3">سرویس مورد نظر یافت نشد.</div>
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// ۳. صرافی آنلاین و تشریفات (Online Exchange)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
function ExchangeServiceView({ service }: { service: ServiceMeta }) {
|
|
const [searchTerm, setSearchTerm] = useState('')
|
|
const [statusFilter, setStatusFilter] = useState<'ALL' | 'CONFIRMED' | 'DONE' | 'PENDING'>('ALL')
|
|
|
|
const rates = [
|
|
{ pair: 'IRR / IQD', name: 'دینار عراق', rate: '0.021', change: '+1.2%', base: '100 هزار تومان = 2,100 دینار', flag: '🇮🇶' },
|
|
{ pair: 'USD / IQD', name: 'دلار به دینار', rate: '1,530', change: '0.0%', base: '1 دلار آمریکا = 1,530 دینار', flag: '💵' },
|
|
{ pair: 'IRR / SAR', name: 'ریال سعودی', rate: '0.000058', change: '-0.4%', base: '100 هزار تومان = 5.8 ریال سعودی', flag: '🇸🇦' },
|
|
{ pair: 'USD / SAR', name: 'دلار به ریال', rate: '3.75', change: '0.0%', base: '1 دلار آمریکا = 3.75 ریال سعودی', flag: '🇸🇦' },
|
|
]
|
|
|
|
const recentOrders = [
|
|
{ id: 981, user: 'محمد حسینی', from: '50,000,000 تومان', to: '1,050,000 دینار عراق', deliverType: 'تحویل نقدی در فرودگاه نجف', status: 'تایید شده و آماده تحویل', statusCode: 'CONFIRMED', date: '1403/07/08' },
|
|
{ id: 982, user: 'علی کاظمی', from: '20,000,000 تومان', to: '420,000 دینار عراق', deliverType: 'حواله به کارت کیکارت عراق', status: 'انجام شد', statusCode: 'DONE', date: '1403/07/08' },
|
|
{ id: 983, user: 'سارا احمدی', from: '100,000,000 تومان', to: '5,800 ریال سعودی', deliverType: 'تحویل نقدی در هتل مدینه', status: 'در حال بررسی', statusCode: 'PENDING', date: '1403/07/07' },
|
|
{ id: 984, user: 'مهدی کریمی', from: '15,000,000 تومان', to: '315,000 دینار عراق', deliverType: 'تحویل نقدی در صحن حضرت زهرا (س)', status: 'انجام شد', statusCode: 'DONE', date: '1403/07/07' },
|
|
{ id: 985, user: 'زهرا موسوی', from: '35,000,000 تومان', to: '2,030 ریال سعودی', deliverType: 'تحویل در فرودگاه جده', status: 'تایید شده و آماده تحویل', statusCode: 'CONFIRMED', date: '1403/07/06' },
|
|
]
|
|
|
|
const filteredOrders = useMemo(() => {
|
|
return recentOrders.filter((ord) => {
|
|
if (statusFilter !== 'ALL' && ord.statusCode !== statusFilter) return false
|
|
if (searchTerm.trim()) {
|
|
const q = searchTerm.toLowerCase().trim()
|
|
const matchUser = ord.user.toLowerCase().includes(q)
|
|
const matchId = String(ord.id).includes(q)
|
|
const matchType = ord.deliverType.toLowerCase().includes(q)
|
|
if (!matchUser && !matchId && !matchType) return false
|
|
}
|
|
return true
|
|
})
|
|
}, [recentOrders, statusFilter, searchTerm])
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* ۱. کارتهای آمار و شاخصهای کلیدی صرافی */}
|
|
<div className="grid grid-cols-2 gap-3.5 sm:grid-cols-4">
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs text-grey-3 font-medium">سفارشات ثبتشده</span>
|
|
<Badge className="bg-primary/10 text-primary border-primary/20 text-[10px] font-bold">
|
|
امروز: {toFa(5)}
|
|
</Badge>
|
|
</div>
|
|
<div className="mt-2 text-2xl font-black text-foreground font-mono tabular-nums">
|
|
{toFa(28)}
|
|
</div>
|
|
<div className="mt-1 text-[11px] text-emerald-600 dark:text-emerald-400 font-medium">
|
|
+12% افزایش نسبت به هفته قبل
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs text-grey-3 font-medium">نرخ مبادله دینار عراق</span>
|
|
<span className="text-xs">🇮🇶</span>
|
|
</div>
|
|
<div className="mt-2 text-2xl font-black text-emerald-600 dark:text-emerald-400 font-mono tabular-nums">
|
|
{toFa('2,100')} <span className="text-xs text-grey-3 font-normal">دینار</span>
|
|
</div>
|
|
<div className="mt-1 text-[11px] text-grey-3 font-medium">
|
|
بهازای هر 100 هزار تومان
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs text-grey-3 font-medium">در انتظار تحویل مقصد</span>
|
|
<Badge className="bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20 text-[10px] font-bold">
|
|
نیازمند اقدام
|
|
</Badge>
|
|
</div>
|
|
<div className="mt-2 text-2xl font-black text-amber-600 dark:text-amber-400 font-mono tabular-nums">
|
|
{toFa(4)}
|
|
</div>
|
|
<div className="mt-1 text-[11px] text-grey-3">
|
|
فرودگاه نجف و هتلهای مدینه
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs text-grey-3 font-medium">حجم تبادلات ارزی</span>
|
|
<Ic name="wallet" className="size-4 text-primary" />
|
|
</div>
|
|
<div className="mt-2 text-2xl font-black text-foreground font-mono tabular-nums">
|
|
{toFa(450)} <span className="text-xs text-grey-3 font-normal">م.تومان</span>
|
|
</div>
|
|
<div className="mt-1 text-[11px] text-grey-3">
|
|
گردش مالی ماه جاری
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ۲. کارتهای لحظهای نرخ برابری ارزها */}
|
|
<div className="space-y-3">
|
|
<div className="flex items-center justify-between">
|
|
<h3 className="text-xs font-bold text-grey-3">تابلوی زنده نرخ برابری ارزهای زیارتی</h3>
|
|
<span className="text-[11px] text-emerald-600 dark:text-emerald-400 font-medium">همگام با بازار نجف و صرافی مرکزی</span>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3.5 sm:grid-cols-4">
|
|
{rates.map((r) => (
|
|
<div key={r.pair} className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-1.5">
|
|
<span className="text-base">{r.flag}</span>
|
|
<span className="text-xs font-bold text-foreground font-mono">{r.pair}</span>
|
|
</div>
|
|
<span className={`text-[11px] font-bold font-mono ${
|
|
r.change.startsWith('+') ? 'text-emerald-600 dark:text-emerald-400' : r.change.startsWith('-') ? 'text-rose-500' : 'text-grey-3'
|
|
}`}>
|
|
{r.change}
|
|
</span>
|
|
</div>
|
|
<div className="mt-2 text-xl font-black text-foreground font-mono tabular-nums">{r.rate}</div>
|
|
<div className="mt-1 text-[11px] text-grey-3 truncate">{r.base}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* ۳. نوار جستجو و فیلترهای سفارشات */}
|
|
<div className="flex flex-col gap-3 rounded-2xl border border-border-soft bg-card/40 p-4 sm:flex-row sm:items-center sm:justify-between shadow-xs">
|
|
<div className="flex items-center gap-2 flex-wrap">
|
|
<button
|
|
onClick={() => setStatusFilter('ALL')}
|
|
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition ${
|
|
statusFilter === 'ALL'
|
|
? 'bg-primary/10 text-primary border border-primary/20 shadow-xs'
|
|
: 'text-grey-3 hover:text-foreground'
|
|
}`}
|
|
>
|
|
همه سفارشات ({toFa(recentOrders.length)})
|
|
</button>
|
|
<button
|
|
onClick={() => setStatusFilter('CONFIRMED')}
|
|
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition ${
|
|
statusFilter === 'CONFIRMED'
|
|
? 'bg-primary/10 text-primary border border-primary/20 shadow-xs'
|
|
: 'text-grey-3 hover:text-foreground'
|
|
}`}
|
|
>
|
|
آماده تحویل
|
|
</button>
|
|
<button
|
|
onClick={() => setStatusFilter('DONE')}
|
|
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition ${
|
|
statusFilter === 'DONE'
|
|
? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border border-emerald-500/20 shadow-xs'
|
|
: 'text-grey-3 hover:text-foreground'
|
|
}`}
|
|
>
|
|
انجام شده
|
|
</button>
|
|
<button
|
|
onClick={() => setStatusFilter('PENDING')}
|
|
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition ${
|
|
statusFilter === 'PENDING'
|
|
? 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20 shadow-xs'
|
|
: 'text-grey-3 hover:text-foreground'
|
|
}`}
|
|
>
|
|
در حال بررسی
|
|
</button>
|
|
</div>
|
|
|
|
<div className="relative min-w-[220px]">
|
|
<Ic name="search" className="absolute start-3 top-1/2 -translate-y-1/2 size-3.5 text-grey-3" />
|
|
<Input
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
placeholder="جستجوی زائر، کد سفارش یا محل تحویل..."
|
|
className="h-9 ps-9 text-xs bg-surface-base rounded-xl border-border-soft"
|
|
/>
|
|
{searchTerm && (
|
|
<button
|
|
onClick={() => setSearchTerm('')}
|
|
className="absolute end-2.5 top-1/2 -translate-y-1/2 text-grey-3 hover:text-foreground p-0.5"
|
|
>
|
|
<Ic name="close" className="size-3" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* ۴. جدول سفارشات ارزی با طراحی استاندارد پنل */}
|
|
<div className="overflow-hidden rounded-2xl border border-border-soft bg-card/40 shadow-xs">
|
|
<div className="flex items-center justify-between border-b border-border-soft bg-card/50 px-5 py-3.5">
|
|
<div className="flex items-center gap-2.5">
|
|
<div className="size-2 rounded-full bg-primary animate-pulse" />
|
|
<span className="text-xs font-black text-foreground">فهرست سفارشات تبدیل و تحویل ارز زیارتی</span>
|
|
<Badge variant="secondary" className="text-[11px] font-mono bg-primary/10 text-primary border-0">
|
|
{toFa(filteredOrders.length)} سفارش
|
|
</Badge>
|
|
</div>
|
|
<span className="text-[11px] text-grey-3 font-mono">آخرین همگامسازی: چند لحظه پیش</span>
|
|
</div>
|
|
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-start text-xs">
|
|
<thead className="border-b border-border-soft bg-card/60 text-grey-3">
|
|
<tr>
|
|
<th className="p-3.5 text-start font-bold w-20">کد پیگیری</th>
|
|
<th className="p-3.5 text-start font-bold">زائر متقاضی</th>
|
|
<th className="p-3.5 text-start font-bold">مبلغ مبدا (تومان)</th>
|
|
<th className="p-3.5 text-start font-bold">مبلغ مقصد (ارز تحویلی)</th>
|
|
<th className="p-3.5 text-start font-bold">محل و نحوه تحویل</th>
|
|
<th className="p-3.5 text-start font-bold w-24">تاریخ</th>
|
|
<th className="p-3.5 text-start font-bold w-36">وضعیت</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-border-soft">
|
|
{filteredOrders.length === 0 ? (
|
|
<tr>
|
|
<td colSpan={7} className="p-8 text-center text-xs text-grey-3">
|
|
هیچ سفارشی با این مشخصات یافت نشد.
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
filteredOrders.map((ord) => (
|
|
<tr key={ord.id} className="hover:bg-card/70 transition-colors">
|
|
<td className="p-3.5 font-mono font-bold text-grey-3">#{ord.id}</td>
|
|
<td className="p-3.5 font-bold text-foreground">
|
|
<div className="flex items-center gap-2">
|
|
<div className="size-6 rounded-full bg-primary/10 text-primary flex items-center justify-center text-[10px] font-bold">
|
|
{ord.user.slice(0, 1)}
|
|
</div>
|
|
<span>{ord.user}</span>
|
|
</div>
|
|
</td>
|
|
<td className="p-3.5 text-grey-2 font-mono tabular-nums font-bold">{ord.from}</td>
|
|
<td className="p-3.5 font-black text-foreground font-mono tabular-nums">{ord.to}</td>
|
|
<td className="p-3.5 text-grey-3">
|
|
<div className="flex items-center gap-1.5">
|
|
<Ic name="mapPin" className="size-3 text-grey-3 shrink-0" />
|
|
<span>{ord.deliverType}</span>
|
|
</div>
|
|
</td>
|
|
<td className="p-3.5 text-grey-3 font-mono text-[11px]">{ord.date}</td>
|
|
<td className="p-3.5">
|
|
<Badge
|
|
className={`text-[10px] rounded-full border ${
|
|
ord.statusCode === 'DONE'
|
|
? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20'
|
|
: ord.statusCode === 'CONFIRMED'
|
|
? 'bg-primary/10 text-primary border-primary/20'
|
|
: 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20'
|
|
}`}
|
|
>
|
|
{ord.status}
|
|
</Badge>
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// ۴. آداب سفر و زیارت (Trip Etiquette)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
function TripEtiquetteServiceView({ service }: { service: ServiceMeta }) {
|
|
const rituals = [
|
|
{ id: 1, title: 'آداب تشرف به حرم امیرالمؤمنین (ع)', city: 'نجف اشرف', prayersCount: 14, audio: true },
|
|
{ id: 2, title: 'زیارت عاشورا، وارث و آداب حرم سیدالشهدا (ع)', city: 'کربلای معلی', prayersCount: 22, audio: true },
|
|
{ id: 3, title: 'اعمال و نمازهای مسجد کوفه و محراب شهادت', city: 'کوفه', prayersCount: 18, audio: true },
|
|
{ id: 4, title: 'آداب احرام، طواف و مناسک عمره مفرده', city: 'مکه مکرمه', prayersCount: 30, audio: true },
|
|
{ id: 5, title: 'زیارت جامعه کبیره و امین الله', city: 'مشترک عتبات', prayersCount: 10, audio: true },
|
|
]
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="grid grid-cols-2 gap-3.5 sm:grid-cols-4">
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<span className="text-xs text-grey-3 font-medium">مجموع زیارات و ادعیه</span>
|
|
<div className="mt-2 text-2xl font-black text-foreground font-mono tabular-nums">{toFa(94)}</div>
|
|
<div className="mt-1 text-[11px] text-grey-3">فهرست کامل مناسک</div>
|
|
</div>
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<span className="text-xs text-grey-3 font-medium">فایلهای صوتی و ترتیل</span>
|
|
<div className="mt-2 text-2xl font-black text-foreground font-mono tabular-nums">{toFa(86)}</div>
|
|
<div className="mt-1 text-[11px] text-emerald-600 dark:text-emerald-400 font-medium">کیفیت استودیویی</div>
|
|
</div>
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<span className="text-xs text-grey-3 font-medium">زبانهای ترجمه</span>
|
|
<div className="mt-2 text-2xl font-black text-primary font-mono tabular-nums">5 زبان</div>
|
|
<div className="mt-1 text-[11px] text-grey-3">فارسی، عربی، انگلیسی، روسی، اندونزیایی</div>
|
|
</div>
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<span className="text-xs text-grey-3 font-medium">شهرهای تحت پوشش</span>
|
|
<div className="mt-2 text-2xl font-black text-foreground font-mono tabular-nums">{toFa(6)}</div>
|
|
<div className="mt-1 text-[11px] text-grey-3">عراق و عربستان سعودی</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
{rituals.map((r) => (
|
|
<div key={r.id} className="rounded-2xl border border-border-soft bg-card/40 p-5 space-y-3 shadow-xs hover:border-primary/30 transition">
|
|
<div className="flex items-center justify-between">
|
|
<Badge variant="outline" className="text-[10px] rounded-lg border-border-soft">{r.city}</Badge>
|
|
<div className="flex items-center gap-1.5 text-[11px] text-primary font-medium">
|
|
<Ic name="volume2" className="size-3.5" />
|
|
<span>همراه با صوت و ترجمه</span>
|
|
</div>
|
|
</div>
|
|
<h3 className="text-base font-black text-foreground">{r.title}</h3>
|
|
<div className="flex items-center justify-between text-xs text-grey-3 pt-2.5 border-t border-border-soft">
|
|
<span>شامل {toFa(r.prayersCount)} فراز و دعای وارده</span>
|
|
<span className="text-primary font-bold hover:underline cursor-pointer">مشاهده متن و صوت ></span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// ۵. مشاوره و پشتیبانی (Consultant / Support)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
function ConsultantServiceView({ service }: { service: ServiceMeta }) {
|
|
const [searchTerm, setSearchTerm] = useState('')
|
|
const [specialtyFilter, setSpecialtyFilter] = useState<string>('ALL')
|
|
|
|
const consultants = [
|
|
{ id: 1, name: 'دکتر علیرضا رضایی', specialty: 'پزشکی سفر، گرمازدگی و مراقبتهای بهداشتی', category: 'medical', status: 'آنلاین و آماده پاسخ', isOnline: true, activeChats: 4, satisfaction: '99%', experience: '8 سال' },
|
|
{ id: 2, name: 'حجتالاسلام سید هادی موسوی', specialty: 'احکام و مناسک حج، عمره و عتبات عالیات', category: 'rituals', status: 'آنلاین و آماده پاسخ', isOnline: true, activeChats: 6, satisfaction: '98%', experience: '12 سال' },
|
|
{ id: 3, name: 'مهندس مهدی کمالی', specialty: 'لجستیک، حملونقل بینشهری، گذرنامه و امور گمشدگان', category: 'logistics', status: 'مشغول در تماس', isOnline: false, activeChats: 2, satisfaction: '97%', experience: '5 سال' },
|
|
{ id: 4, name: 'سرکار خانم فاطمه سعیدی', specialty: 'مشاوره خانواده، بانوان، اقامت و کودکان در سفر', category: 'family', status: 'آنلاین و آماده پاسخ', isOnline: true, activeChats: 3, satisfaction: '100%', experience: '6 سال' },
|
|
]
|
|
|
|
const filteredConsultants = useMemo(() => {
|
|
return consultants.filter((c) => {
|
|
if (specialtyFilter !== 'ALL' && c.category !== specialtyFilter) return false
|
|
if (searchTerm.trim()) {
|
|
const q = searchTerm.toLowerCase().trim()
|
|
const matchName = c.name.toLowerCase().includes(q)
|
|
const matchSpec = c.specialty.toLowerCase().includes(q)
|
|
if (!matchName && !matchSpec) return false
|
|
}
|
|
return true
|
|
})
|
|
}, [consultants, specialtyFilter, searchTerm])
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* ۱. کارتهای شاخص عملکرد مرکز پشتیبانی و مشاوره */}
|
|
<div className="grid grid-cols-2 gap-3.5 sm:grid-cols-4">
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs text-grey-3 font-medium">مشاوران شیفت جاری</span>
|
|
<span className="flex items-center gap-1.5 text-[10px] text-emerald-600 dark:text-emerald-400 font-bold">
|
|
<span className="size-2 rounded-full bg-emerald-500 animate-pulse" />
|
|
آنلاین
|
|
</span>
|
|
</div>
|
|
<div className="mt-2 text-2xl font-black text-foreground font-mono tabular-nums">
|
|
{toFa(8)} <span className="text-xs text-grey-3 font-normal">نفر</span>
|
|
</div>
|
|
<div className="mt-1 text-[11px] text-grey-3 font-medium">
|
|
از 12 کارشناس پشتیبانی 24 ساعته
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs text-grey-3 font-medium">مشاورههای امروز</span>
|
|
<Badge className="bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20 text-[10px] font-bold">
|
|
موفق
|
|
</Badge>
|
|
</div>
|
|
<div className="mt-2 text-2xl font-black text-emerald-600 dark:text-emerald-400 font-mono tabular-nums">
|
|
{toFa(64)}
|
|
</div>
|
|
<div className="mt-1 text-[11px] text-grey-3 font-medium">
|
|
پاسخگویی به مسائل بهداشتی و شرعی
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs text-grey-3 font-medium">میانگین زمان انتظار</span>
|
|
<Ic name="clock" className="size-3.5 text-primary" />
|
|
</div>
|
|
<div className="mt-2 text-2xl font-black text-foreground font-mono tabular-nums">
|
|
45 <span className="text-xs text-grey-3 font-normal">ثانیه</span>
|
|
</div>
|
|
<div className="mt-1 text-[11px] text-emerald-600 dark:text-emerald-400 font-medium">
|
|
استاندارد عالی (زیر 1 دقیقه)
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs text-grey-3 font-medium">رضایتمندی زائرین</span>
|
|
<Ic name="star" className="size-3.5 text-amber-500" />
|
|
</div>
|
|
<div className="mt-2 text-2xl font-black text-foreground font-mono tabular-nums">
|
|
98.7%
|
|
</div>
|
|
<div className="mt-1 text-[11px] text-grey-3 font-medium">
|
|
بر اساس 1,420 بازخورد ثبتشده
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ۲. فیلترها و نوار جستجوی کارشناسان */}
|
|
<div className="flex flex-col gap-3 rounded-2xl border border-border-soft bg-card/40 p-4 sm:flex-row sm:items-center sm:justify-between shadow-xs">
|
|
<div className="flex items-center gap-2 flex-wrap">
|
|
<button
|
|
onClick={() => setSpecialtyFilter('ALL')}
|
|
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition ${
|
|
specialtyFilter === 'ALL'
|
|
? 'bg-primary/10 text-primary border border-primary/20 shadow-xs'
|
|
: 'text-grey-3 hover:text-foreground'
|
|
}`}
|
|
>
|
|
همه تخصصها
|
|
</button>
|
|
<button
|
|
onClick={() => setSpecialtyFilter('medical')}
|
|
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition ${
|
|
specialtyFilter === 'medical'
|
|
? 'bg-primary/10 text-primary border border-primary/20 shadow-xs'
|
|
: 'text-grey-3 hover:text-foreground'
|
|
}`}
|
|
>
|
|
پزشکی و بهداشت
|
|
</button>
|
|
<button
|
|
onClick={() => setSpecialtyFilter('rituals')}
|
|
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition ${
|
|
specialtyFilter === 'rituals'
|
|
? 'bg-primary/10 text-primary border border-primary/20 shadow-xs'
|
|
: 'text-grey-3 hover:text-foreground'
|
|
}`}
|
|
>
|
|
احکام و مناسک
|
|
</button>
|
|
<button
|
|
onClick={() => setSpecialtyFilter('logistics')}
|
|
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition ${
|
|
specialtyFilter === 'logistics'
|
|
? 'bg-primary/10 text-primary border border-primary/20 shadow-xs'
|
|
: 'text-grey-3 hover:text-foreground'
|
|
}`}
|
|
>
|
|
لجستیک و گمشدگان
|
|
</button>
|
|
</div>
|
|
|
|
<div className="relative min-w-[220px]">
|
|
<Ic name="search" className="absolute start-3 top-1/2 -translate-y-1/2 size-3.5 text-grey-3" />
|
|
<Input
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
placeholder="جستجوی نام یا حوزه تخصص..."
|
|
className="h-9 ps-9 text-xs bg-surface-base rounded-xl border-border-soft"
|
|
/>
|
|
{searchTerm && (
|
|
<button
|
|
onClick={() => setSearchTerm('')}
|
|
className="absolute end-2.5 top-1/2 -translate-y-1/2 text-grey-3 hover:text-foreground p-0.5"
|
|
>
|
|
<Ic name="close" className="size-3" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* ۳. کارتهای کارشناسان با استایل مدرن */}
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<h3 className="text-sm font-black text-foreground">کادر کارشناسان پشتیبانی و مشاوره عقیله</h3>
|
|
<Badge variant="secondary" className="text-[11px] font-mono bg-primary/10 text-primary border-0">
|
|
{toFa(filteredConsultants.length)} کارشناس
|
|
</Badge>
|
|
</div>
|
|
<span className="text-xs text-grey-3">پشتیبانی زنده صوتی و متنی زائرین</span>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
{filteredConsultants.map((c) => (
|
|
<div
|
|
key={c.id}
|
|
className="rounded-2xl border border-border-soft bg-card/40 p-5 space-y-4 shadow-xs hover:border-primary/40 transition flex flex-col justify-between"
|
|
>
|
|
<div className="space-y-3">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div className="size-11 rounded-2xl bg-primary/10 text-primary flex items-center justify-center font-bold text-base shrink-0">
|
|
<Ic name="headset" className="size-5" />
|
|
</div>
|
|
<Badge
|
|
className={`text-[10px] rounded-full border ${
|
|
c.isOnline
|
|
? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20'
|
|
: 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20'
|
|
}`}
|
|
>
|
|
{c.status}
|
|
</Badge>
|
|
</div>
|
|
|
|
<div>
|
|
<h4 className="text-sm font-black text-foreground">{c.name}</h4>
|
|
<p className="text-xs text-grey-3 mt-1 leading-relaxed line-clamp-2">{c.specialty}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-3 pt-3 border-t border-border-soft text-xs">
|
|
<div className="flex items-center justify-between text-grey-3">
|
|
<span>گفتگوهای فعال:</span>
|
|
<span className="font-mono font-bold text-foreground tabular-nums">{toFa(c.activeChats)} جلسه</span>
|
|
</div>
|
|
<div className="flex items-center justify-between text-grey-3">
|
|
<span>رضایت مخاطبان:</span>
|
|
<span className="font-mono font-bold text-emerald-600 dark:text-emerald-400 tabular-nums">{c.satisfaction}</span>
|
|
</div>
|
|
<div className="flex items-center justify-between text-grey-3">
|
|
<span>سابقه تخصصی:</span>
|
|
<span className="font-mono text-grey-2">{c.experience}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// ۶. فروشگاه معصومه (Massoumah Store) در components/StoreServiceView پیادهسازی شده است
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// ۷. تخمین هزینه سفر (Cost Estimate)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
function CostEstimateServiceView({ service }: { service: ServiceMeta }) {
|
|
const [selectedDestination, setSelectedDestination] = useState<string>('ALL')
|
|
const [searchTerm, setSearchTerm] = useState('')
|
|
|
|
const estimateCategories = [
|
|
{ id: 1, title: 'هتل و اقامتگاه (نجف / کربلا / مکه / مدینه)', category: 'stay', standard: 'شبی 25 الی 40 دلار', vip: 'شبی 60 الی 120 دلار', notes: 'شامل صبحانه، اینترنت رایگان و نزدیکی به حرم' },
|
|
{ id: 2, title: 'ترانسفر بین شهری و فرودگاهی (ون و سواری)', category: 'transfer', standard: 'هر مسیر 10 الی 15 دلار', vip: 'خودروی اختصاصی 30 الی 50 دلار', notes: 'مسیر نجف به کربلا، کاظمین، سامرا و بغداد' },
|
|
{ id: 3, title: 'خوراک و پذیرایی روزانه', category: 'food', standard: 'روزانه 10 الی 15 دلار', vip: 'روزانه 25 الی 40 دلار', notes: 'رستورانهای منتخب، غذای ایرانی و بوفه هتل' },
|
|
{ id: 4, title: 'اینترنت و ارتباطات سیمکارت', category: 'simcard', standard: '10 الی 15 دلار برای کل سفر', vip: '25 دلار بسته نامحدود', notes: 'سیمکارتهای زین، آسیاسل و کورک با دیتا پرسرعت' },
|
|
{ id: 5, title: 'ویزا، عوارض خروج و بیمه زائر', category: 'visa', standard: 'رایگان (عراق) / 120 دلار (عربستان)', vip: 'ویزای تجاری و ترانزیت VIP', notes: 'بیمه درمان و حوادث تا سقف تعهدات کامل' },
|
|
]
|
|
|
|
const filteredCategories = useMemo(() => {
|
|
return estimateCategories.filter((c) => {
|
|
if (selectedDestination !== 'ALL' && c.category !== selectedDestination) return false
|
|
if (searchTerm.trim()) {
|
|
const q = searchTerm.toLowerCase().trim()
|
|
const matchTitle = c.title.toLowerCase().includes(q)
|
|
const matchNotes = c.notes.toLowerCase().includes(q)
|
|
if (!matchTitle && !matchNotes) return false
|
|
}
|
|
return true
|
|
})
|
|
}, [estimateCategories, selectedDestination, searchTerm])
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* ۱. کارتهای شاخصهای آماری تخمین هزینه */}
|
|
<div className="grid grid-cols-2 gap-3.5 sm:grid-cols-4">
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs text-grey-3 font-medium">استعلامهای این ماه</span>
|
|
<Badge className="bg-primary/10 text-primary border-primary/20 text-[10px] font-bold">
|
|
زائرین
|
|
</Badge>
|
|
</div>
|
|
<div className="mt-2 text-2xl font-black text-foreground font-mono tabular-nums">
|
|
{toFa('12.8K')}
|
|
</div>
|
|
<div className="mt-1 text-[11px] text-emerald-600 dark:text-emerald-400 font-medium">
|
|
+18% رشد استعلامهای اربعین و عمره
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs text-grey-3 font-medium">دقت برآورد هوشمند</span>
|
|
<Ic name="check" className="size-3.5 text-emerald-600" />
|
|
</div>
|
|
<div className="mt-2 text-2xl font-black text-emerald-600 dark:text-emerald-400 font-mono tabular-nums">
|
|
96.2%
|
|
</div>
|
|
<div className="mt-1 text-[11px] text-grey-3 font-medium">
|
|
مطابقت با هزینههای واقعی سفر
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs text-grey-3 font-medium">سرفصلهای هزینهای</span>
|
|
<Badge variant="outline" className="text-[10px] border-border-soft">
|
|
پوشش کامل
|
|
</Badge>
|
|
</div>
|
|
<div className="mt-2 text-2xl font-black text-foreground font-mono tabular-nums">
|
|
{toFa(estimateCategories.length)} <span className="text-xs text-grey-3 font-normal">بخش اصلی</span>
|
|
</div>
|
|
<div className="mt-1 text-[11px] text-grey-3 font-medium">
|
|
هتل، ترانسفر، خوراک، اینترنت، بیمه
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-2xl border border-border-soft bg-card/40 p-4 shadow-xs hover:border-primary/30 transition backdrop-blur-xs">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs text-grey-3 font-medium">وضعیت نرخها</span>
|
|
<span className="size-2 rounded-full bg-emerald-500 animate-pulse" />
|
|
</div>
|
|
<div className="mt-2 text-2xl font-black text-primary font-mono tabular-nums">
|
|
امروز
|
|
</div>
|
|
<div className="mt-1 text-[11px] text-grey-3 font-medium">
|
|
همگامسازی لحظهای با هتلها و ترابری
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ۲. فیلترها و نوار جستجو */}
|
|
<div className="flex flex-col gap-3 rounded-2xl border border-border-soft bg-card/40 p-4 sm:flex-row sm:items-center sm:justify-between shadow-xs">
|
|
<div className="flex items-center gap-2 flex-wrap">
|
|
<button
|
|
onClick={() => setSelectedDestination('ALL')}
|
|
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition ${
|
|
selectedDestination === 'ALL'
|
|
? 'bg-primary/10 text-primary border border-primary/20 shadow-xs'
|
|
: 'text-grey-3 hover:text-foreground'
|
|
}`}
|
|
>
|
|
همه سرفصلها ({toFa(estimateCategories.length)})
|
|
</button>
|
|
<button
|
|
onClick={() => setSelectedDestination('stay')}
|
|
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition ${
|
|
selectedDestination === 'stay'
|
|
? 'bg-primary/10 text-primary border border-primary/20 shadow-xs'
|
|
: 'text-grey-3 hover:text-foreground'
|
|
}`}
|
|
>
|
|
هتل و اقامتگاه
|
|
</button>
|
|
<button
|
|
onClick={() => setSelectedDestination('transfer')}
|
|
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition ${
|
|
selectedDestination === 'transfer'
|
|
? 'bg-primary/10 text-primary border border-primary/20 shadow-xs'
|
|
: 'text-grey-3 hover:text-foreground'
|
|
}`}
|
|
>
|
|
ترانسفر و تردد
|
|
</button>
|
|
<button
|
|
onClick={() => setSelectedDestination('food')}
|
|
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition ${
|
|
selectedDestination === 'food'
|
|
? 'bg-primary/10 text-primary border border-primary/20 shadow-xs'
|
|
: 'text-grey-3 hover:text-foreground'
|
|
}`}
|
|
>
|
|
خوراک و پذیرایی
|
|
</button>
|
|
</div>
|
|
|
|
<div className="relative min-w-[220px]">
|
|
<Ic name="search" className="absolute start-3 top-1/2 -translate-y-1/2 size-3.5 text-grey-3" />
|
|
<Input
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
placeholder="جستجوی سرفصل یا نکات هزینه..."
|
|
className="h-9 ps-9 text-xs bg-surface-base rounded-xl border-border-soft"
|
|
/>
|
|
{searchTerm && (
|
|
<button
|
|
onClick={() => setSearchTerm('')}
|
|
className="absolute end-2.5 top-1/2 -translate-y-1/2 text-grey-3 hover:text-foreground p-0.5"
|
|
>
|
|
<Ic name="close" className="size-3" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* ۳. جدول تخمین و بودجهبندی سفر با هدر کارتی مدرن */}
|
|
<div className="overflow-hidden rounded-2xl border border-border-soft bg-card/40 shadow-xs">
|
|
<div className="flex items-center justify-between border-b border-border-soft bg-card/50 px-5 py-3.5">
|
|
<div className="flex items-center gap-2.5">
|
|
<Ic name="calculator" className="size-4 text-primary" />
|
|
<span className="text-xs font-black text-foreground">جدول برآورد تفکیکی مخارج سفر (اقتصادی و VIP)</span>
|
|
<Badge variant="secondary" className="text-[11px] font-mono bg-primary/10 text-primary border-0">
|
|
{toFa(filteredCategories.length)} آیتم
|
|
</Badge>
|
|
</div>
|
|
<span className="text-[11px] text-grey-3">ارز مبنا: دلار و دینار</span>
|
|
</div>
|
|
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-start text-xs">
|
|
<thead className="border-b border-border-soft bg-card/60 text-grey-3">
|
|
<tr>
|
|
<th className="p-3.5 text-start font-bold">بخش هزینه</th>
|
|
<th className="p-3.5 text-start font-bold">بسته اقتصادی (Standard)</th>
|
|
<th className="p-3.5 text-start font-bold">بسته ویژه (VIP)</th>
|
|
<th className="p-3.5 text-start font-bold">توضیحات و نکات اجرایی</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-border-soft">
|
|
{filteredCategories.map((c) => (
|
|
<tr key={c.id} className="hover:bg-card/70 transition-colors">
|
|
<td className="p-3.5 font-bold text-foreground">{c.title}</td>
|
|
<td className="p-3.5 font-mono text-grey-2 tabular-nums">{c.standard}</td>
|
|
<td className="p-3.5 font-mono font-black text-primary tabular-nums">{c.vip}</td>
|
|
<td className="p-3.5 text-grey-3 leading-relaxed">{c.notes}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|