import React, { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { toast } from 'sonner'; import { ArrowUpCircleIcon, ArrowDownCircleIcon, DevicePhoneMobileIcon, ClockIcon, CheckCircleIcon, XCircleIcon, BellAlertIcon, ChatBubbleLeftEllipsisIcon, } from '@heroicons/react/24/outline'; import { api } from '../lib/api'; import type { ApiResponse, PaginatedResponse } from '../lib/api'; import type { SmsWalletBalance, SmsWalletLog, SmsSettings } from '../types'; import { usePaymentConfig } from '../hooks/usePaymentConfig'; import { formatRial, formatNumber, formatDateTime } from '../lib/utils'; import Modal from '../components/ui/Modal'; import Pagination from '../components/ui/Pagination'; import PageHeader from '../components/ui/PageHeader'; import FeatureGate from '../components/ui/FeatureGate'; const chargeSchema = z.object({ amount_rials: z.coerce.number().min(10000, 'حداقل مبلغ ۱۰,۰۰۰ ریال است'), }); type ChargeForm = z.infer; const EMPTY_LOGS: SmsWalletLog[] = []; function SmsWalletPageInner() { const qc = useQueryClient(); const [chargeOpen, setChargeOpen] = useState(false); const [gateway, setGateway] = useState<'mellat' | 'sep'>('mellat'); const [logPage, setLogPage] = useState(1); const { data: balanceData, isLoading: balanceLoading } = useQuery>({ queryKey: ['sms-wallet-balance'], queryFn: () => api.get('/api/v1/sms/wallet/balance'), }); const { data: logsData, isLoading: logsLoading } = useQuery>({ queryKey: ['sms-wallet-logs', logPage], queryFn: () => api.get(`/api/v1/sms/wallet/logs?page=${logPage}&limit=20`), }); const { data: settingsData } = useQuery>({ queryKey: ['sms-settings'], queryFn: () => api.get('/api/v1/sms/settings'), }); const { isTestMode } = usePaymentConfig(); const balance = balanceData?.data; const logs = logsData?.data ?? EMPTY_LOGS; const total = logsData?.meta?.totalRecords ?? 0; const settings = settingsData?.data; const chargeForm = useForm({ resolver: zodResolver(chargeSchema) }); const watchAmount = chargeForm.watch('amount_rials'); const chargeMutation = useMutation({ mutationFn: ({ amount_rials }: ChargeForm) => api.post<{ data: { payment_url: string } }>('/api/v1/sms/wallet/charge', { gateway, amount_rials }), onSuccess: (res: any) => { const url = res?.data?.redirect_url ?? res?.data?.payment_url; if (url) window.location.href = url; else toast.error('خطا در دریافت لینک پرداخت'); }, onError: (e: any) => toast.error(e.message), }); const [localSettings, setLocalSettings] = useState(null); const currentSettings = localSettings ?? settings; const saveMutation = useMutation({ mutationFn: (body: SmsSettings) => api.patch('/api/v1/sms/settings', body), onSuccess: () => { qc.invalidateQueries({ queryKey: ['sms-settings'] }); toast.success('تنظیمات ذخیره شد'); }, onError: (e: any) => toast.error(e.message), }); const smsCount = balance?.estimated_sms_count ?? 0; const isLowBalance = smsCount < 10; return ( <> {/* ردیف بالا: موجودی + آمار */}
{/* کارت موجودی — gradient */}
موجودی کیف پول
{isLowBalance ? 'موجودی کم' : 'فعال'}
{balanceLoading ?
: ( <>
{formatRial(balance?.balance_rials ?? 0)}
معادل {formatNumber(smsCount)} پیامک {balance?.sms_price_rials ? ` (هر پیامک ${formatRial(balance.sms_price_rials)})` : ''}
)}
{/* آمار پیامک */}
{formatNumber(smsCount)}
پیامک باقی‌مانده
{currentSettings?.reminder_enabled ? `${currentSettings.reminder_hours_before} ساعت` : '—'}
یادآوری قبل از نوبت
{/* وضعیت سرویس */}
وضعیت سرویس‌ها
{[ { label: 'یادآوری نوبت', on: currentSettings?.reminder_enabled ?? false }, { label: 'پیامک بعد از ویزیت', on: currentSettings?.post_visit_enabled ?? false }, ].map(({ label, on }) => (
{label} {on ? 'فعال' : 'غیرفعال'}
))}
{/* تنظیمات ارسال — full width */}
تنظیمات ارسال پیامک
{currentSettings ? (
{/* ردیف: یادآوری قبل از نوبت */}
یادآوری قبل از نوبت
پیامک یادآور به بیمار چند ساعت قبل از وقت نوبت ارسال می‌شود
{currentSettings.reminder_enabled && (
setLocalSettings({ ...currentSettings, reminder_hours_before: +e.target.value })} dir="ltr" style={{ width: 72, textAlign: 'center' }} />
)}
setLocalSettings({ ...currentSettings, reminder_enabled: !currentSettings.reminder_enabled })} />
{/* ردیف: پیامک بعد از ویزیت */}
{/* header ردیف */}
پیامک بعد از ویزیت
متن تشکر پس از پایان مراجعه برای بیمار ارسال می‌شود
setLocalSettings({ ...currentSettings, post_visit_enabled: !currentSettings.post_visit_enabled })} />
{/* محتوای باز‌شونده */} {currentSettings.post_visit_enabled && (
متن پیامک