import React from 'react'; import { useQuery } from '@tanstack/react-query'; import { Link } from 'react-router-dom'; import { UserGroupIcon, HeartIcon, BuildingOffice2Icon, CalendarDaysIcon, CreditCardIcon, ChatBubbleLeftEllipsisIcon, BanknotesIcon, ArrowPathIcon, UserPlusIcon, } from '@heroicons/react/24/outline'; import { api } from '../lib/api'; import type { ApiResponse } from '../lib/api'; import { formatNumber, formatRial, formatDateTime } from '../lib/utils'; interface DashboardStats { total_users: number; active_doctors: number; total_doctors: number; total_clinics: number; today_appointments: number; total_appointments: number; today_payments_count: number; today_payments_amount: number; total_payments_amount: number; pending_comments: number; pending_settlements: number; } interface RecentAppointment { uuid: string; slot_start: string; status: string; doctor_name: string; user_mobile: string; user_name: string | null; created_at: string; } interface RecentPayment { uuid: string; amount: number; status: string; gateway: string; user_mobile: string; user_name: string | null; created_at: string; } interface RecentUser { uuid: string; mobile: string; name: string | null; email: string | null; created_at: string; } interface RecentData { appointments: RecentAppointment[]; payments: RecentPayment[]; users: RecentUser[]; } const APPT_STATUS: Record = { waiting_for_payment: { label: 'انتظار پرداخت', cls: 'bg-yellow-100 dark:bg-yellow-400/10 text-yellow-700 dark:text-yellow-300' }, reserved: { label: 'رزرو شده', cls: 'bg-blue-100 dark:bg-blue-400/10 text-blue-700 dark:text-blue-300' }, checked_in: { label: 'ورود به مطب', cls: 'bg-indigo-100 dark:bg-indigo-400/10 text-indigo-700 dark:text-indigo-300' }, waiting: { label: 'صف انتظار', cls: 'bg-orange-100 dark:bg-orange-400/10 text-orange-700 dark:text-orange-300' }, in_progress: { label: 'در حال ویزیت', cls: 'bg-purple-100 dark:bg-purple-400/10 text-purple-700 dark:text-purple-300' }, visited: { label: 'ویزیت شده', cls: 'bg-emerald-100 dark:bg-emerald-400/10 text-emerald-700 dark:text-emerald-300' }, completed: { label: 'تکمیل شده', cls: 'bg-green-100 dark:bg-green-400/10 text-green-700 dark:text-green-300' }, cancelled_by_doctor: { label: 'لغو پزشک', cls: 'bg-red-100 dark:bg-red-400/10 text-red-700 dark:text-red-300' }, cancelled_by_user: { label: 'لغو بیمار', cls: 'bg-red-100 dark:bg-red-400/10 text-red-700 dark:text-red-300' }, auto_cancel_unpaid: { label: 'لغو خودکار', cls: 'bg-slate-100 dark:bg-slate-400/10 text-slate-600 dark:text-slate-400' }, no_show: { label: 'غیبت', cls: 'bg-slate-100 dark:bg-slate-400/10 text-slate-600 dark:text-slate-400' }, }; const PAY_STATUS: Record = { pending: { label: 'در انتظار', cls: 'bg-yellow-100 dark:bg-yellow-400/10 text-yellow-700 dark:text-yellow-300' }, received: { label: 'موفق', cls: 'bg-green-100 dark:bg-green-400/10 text-green-700 dark:text-green-300' }, canceled: { label: 'لغو شده', cls: 'bg-red-100 dark:bg-red-400/10 text-red-700 dark:text-red-300' }, refund: { label: 'استرداد', cls: 'bg-blue-100 dark:bg-blue-400/10 text-blue-700 dark:text-blue-300' }, }; function MicroBadge({ status, map }: { status: string; map: Record }) { const s = map[status] ?? { label: status, cls: 'bg-slate-100 dark:bg-slate-400/10 text-slate-600 dark:text-slate-400' }; return ( {s.label} ); } interface StatCard { label: string; value: string; sub?: string; icon: React.ElementType; iconBg: string; iconColor: string; } function StatCardSkeleton() { return (
); } function RecentSkeleton() { return (
{[...Array(4)].map((_, i) => (
))}
); } export default function DashboardPage() { const statsQ = useQuery({ queryKey: ['dashboard-stats'], queryFn: () => api.get>('/api/v1/admin/dashboard/stats'), staleTime: 60_000, }); const recentQ = useQuery({ queryKey: ['dashboard-recent'], queryFn: () => api.get>('/api/v1/admin/dashboard/recent'), staleTime: 30_000, }); const stats: DashboardStats | undefined = (statsQ.data?.data as any)?.data ?? statsQ.data?.data; const recent: RecentData | undefined = (recentQ.data?.data as any)?.data ?? recentQ.data?.data; const fn = (n?: number) => (n !== undefined ? formatNumber(n) : '—'); const cards: StatCard[] = [ { label: 'کل کاربران', value: fn(stats?.total_users), icon: UserGroupIcon, iconBg: 'bg-violet-100 dark:bg-violet-400/10', iconColor: 'text-violet-600 dark:text-violet-400' }, { label: 'پزشکان فعال', value: fn(stats?.active_doctors), sub: stats ? `از ${fn(stats.total_doctors)} پزشک` : undefined, icon: HeartIcon, iconBg: 'bg-emerald-100 dark:bg-emerald-400/10', iconColor: 'text-emerald-600 dark:text-emerald-400' }, { label: 'کلینیک‌ها', value: fn(stats?.total_clinics), icon: BuildingOffice2Icon, iconBg: 'bg-blue-100 dark:bg-blue-400/10', iconColor: 'text-blue-600 dark:text-blue-400' }, { label: 'نوبت‌های امروز', value: fn(stats?.today_appointments), sub: stats ? `مجموع: ${fn(stats.total_appointments)}` : undefined, icon: CalendarDaysIcon, iconBg: 'bg-orange-100 dark:bg-orange-400/10', iconColor: 'text-orange-600 dark:text-orange-400' }, { label: 'درآمد امروز', value: stats?.today_payments_amount !== undefined ? formatRial(stats.today_payments_amount) : '—', sub: stats ? `${fn(stats.today_payments_count)} تراکنش` : undefined, icon: CreditCardIcon, iconBg: 'bg-pink-100 dark:bg-pink-400/10', iconColor: 'text-pink-600 dark:text-pink-400' }, { label: 'کل درآمد', value: stats?.total_payments_amount !== undefined ? formatRial(stats.total_payments_amount) : '—', icon: CreditCardIcon, iconBg: 'bg-indigo-100 dark:bg-indigo-400/10', iconColor: 'text-indigo-600 dark:text-indigo-400' }, { label: 'نظرات در انتظار', value: fn(stats?.pending_comments), icon: ChatBubbleLeftEllipsisIcon, iconBg: 'bg-yellow-100 dark:bg-yellow-400/10', iconColor: 'text-yellow-600 dark:text-yellow-400' }, { label: 'درخواست تسویه', value: fn(stats?.pending_settlements), icon: BanknotesIcon, iconBg: 'bg-teal-100 dark:bg-teal-400/10', iconColor: 'text-teal-600 dark:text-teal-400' }, ]; return (
{/* Header */}

داشبورد

خلاصه وضعیت سیستم

{statsQ.isError && ( )}
{statsQ.isError && (
خطا در دریافت آمار — اطلاعات ممکن است به‌روز نباشند.
)} {/* Stats grid */}
{statsQ.isLoading ? Array.from({ length: 8 }).map((_, i) => ) : cards.map((c) => (

{c.label}

{c.value}

{c.sub &&

{c.sub}

}
)) }
{/* Recent activities */}
{/* Recent Appointments */}

آخرین نوبت‌ها

مشاهده همه
{recentQ.isLoading ? : recentQ.isError ? (

خطا در دریافت اطلاعات

) : !recent?.appointments?.length ? (

نوبتی ثبت نشده

) : (
    {recent.appointments.map((a) => (
  • {a.user_name || a.user_mobile}

    دکتر {a.doctor_name}

    {formatDateTime(a.slot_start)}

  • ))}
)}
{/* Recent Payments */}

آخرین پرداخت‌ها

مشاهده همه
{recentQ.isLoading ? : recentQ.isError ? (

خطا در دریافت اطلاعات

) : !recent?.payments?.length ? (

پرداختی ثبت نشده

) : (
    {recent.payments.map((p) => (
  • {p.user_name || p.user_mobile}

    {formatRial(p.amount)}

    {formatDateTime(p.created_at)}

  • ))}
)}
{/* Recent Users */}

کاربران جدید

مشاهده همه
{recentQ.isLoading ? : recentQ.isError ? (

خطا در دریافت اطلاعات

) : !recent?.users?.length ? (

کاربری ثبت نشده

) : (
    {recent.users.map((u) => (
  • {u.name || u.mobile}

    {u.name &&

    {u.mobile}

    }

    {formatDateTime(u.created_at)}

  • ))}
)}
); }