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

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">مشاهده متن و صوت &gt;</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>
)
}