import React, { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { CheckIcon, XMarkIcon, TrashIcon, PlusIcon } from '@heroicons/react/24/outline'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { toast } from 'sonner'; import { api } from '../lib/api'; import type { ApiResponse, PaginatedResponse } from '../lib/api'; import type { SmsTemplate, SmsLog } from '../types'; import { formatDate, formatDateTime } from '../lib/utils'; import DataTable, { Column } from '../components/ui/DataTable'; import StatusBadge from '../components/ui/StatusBadge'; import Pagination from '../components/ui/Pagination'; import ConfirmDialog from '../components/ui/ConfirmDialog'; import Modal from '../components/ui/Modal'; const templateSchema = z.object({ name: z.string().min(2, 'نام قالب الزامی است'), body: z.string().min(5, 'متن قالب الزامی است'), }); type TemplateFormData = z.infer; type Tab = 'samples' | 'pending' | 'logs' | 'post-visit-review'; const STATUS_LOG_META: Record = { sent: { label: 'ارسال شده', cls: 'green' }, failed: { label: 'ناموفق', cls: 'red' }, queued: { label: 'در صف', cls: 'amber' }, }; export default function SmsPage() { const qc = useQueryClient(); const [activeTab, setActiveTab] = useState('samples'); const [page, setPage] = useState(1); const [approveTarget, setApproveTarget] = useState(null); const [rejectTarget, setRejectTarget] = useState(null); const [rejectReason, setRejectReason] = useState(''); const [addOpen, setAddOpen] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); const [postVisitRejectId, setPostVisitRejectId] = useState(null); const [postVisitRejectReason, setPostVisitRejectReason] = useState(''); const limit = 15; const sampleTemplatesQuery = useQuery({ queryKey: ['sms-samples', page], queryFn: () => api.get>( `/api/v1/admin/sms/sample-templates?status=approved&page=${page}&limit=${limit}`, ), enabled: activeTab === 'samples', }); const pendingTemplatesQuery = useQuery({ queryKey: ['sms-pending', activeTab === 'pending' ? page : 1], queryFn: () => api.get>( `/api/v1/admin/sms/sample-templates?status=pending&page=${activeTab === 'pending' ? page : 1}&limit=${limit}`, ), }); const logsQuery = useQuery({ queryKey: ['sms-logs', page], queryFn: () => api.get>(`/api/v1/admin/sms/logs?page=${page}&limit=${limit}`), enabled: activeTab === 'logs', }); const { register, handleSubmit, reset, formState: { errors, isSubmitting } } = useForm({ resolver: zodResolver(templateSchema), }); const createMutation = useMutation({ mutationFn: (d: TemplateFormData) => api.post>('/api/v1/sms/template', d), onSuccess: () => { toast.success('قالب اضافه شد'); setAddOpen(false); reset(); qc.invalidateQueries({ queryKey: ['sms-samples'] }); }, onError: (err: Error) => toast.error(err.message), }); const approveMutation = useMutation({ mutationFn: (t: SmsTemplate) => api.post>(`/api/v1/admin/sms/template/${t.uuid}/approve`, {}), onSuccess: () => { toast.success('قالب تأیید شد'); setApproveTarget(null); qc.invalidateQueries({ queryKey: ['sms-pending'] }); }, onError: (err: Error) => toast.error(err.message), }); const rejectMutation = useMutation({ mutationFn: ({ t, reason }: { t: SmsTemplate; reason: string }) => api.post>(`/api/v1/admin/sms/template/${t.uuid}/reject`, { reason }), onSuccess: () => { toast.success('قالب رد شد'); setRejectTarget(null); setRejectReason(''); qc.invalidateQueries({ queryKey: ['sms-pending'] }); }, onError: (err: Error) => toast.error(err.message), }); const deleteMutation = useMutation({ mutationFn: (t: SmsTemplate) => api.delete>(`/api/v1/sms/template/${t.uuid}`), onSuccess: () => { toast.success('قالب حذف شد'); setDeleteTarget(null); qc.invalidateQueries({ queryKey: ['sms-samples'] }); }, onError: (err: Error) => toast.error(err.message), }); const postVisitReviewQuery = useQuery }>>({ queryKey: ['sms-post-visit-review'], queryFn: () => api.get('/api/v1/admin/sms/settings/review'), enabled: activeTab === 'post-visit-review', }); const postVisitApproveMutation = useMutation({ mutationFn: (id: number) => api.post(`/api/v1/admin/sms/settings/${id}/approve`, {}), onSuccess: () => { toast.success('متن پیامک تأیید شد'); qc.invalidateQueries({ queryKey: ['sms-post-visit-review'] }); }, onError: (err: Error) => toast.error(err.message), }); const postVisitRejectMutation = useMutation({ mutationFn: ({ id, reason }: { id: number; reason: string }) => api.post(`/api/v1/admin/sms/settings/${id}/reject`, { reason }), onSuccess: () => { toast.success('متن پیامک رد شد'); setPostVisitRejectId(null); setPostVisitRejectReason(''); qc.invalidateQueries({ queryKey: ['sms-post-visit-review'] }); }, onError: (err: Error) => toast.error(err.message), }); const templateColumns: Column[] = [ { key: 'name', header: 'نام', render: (t) => {t.name} }, { key: 'body', header: 'محتوا', render: (t) => {t.body}, }, { key: 'status', header: 'وضعیت', render: (t) => }, { key: 'created_at', header: 'تاریخ', render: (t) => formatDate(t.created_at) }, ]; const pendingColumns: Column[] = templateColumns.filter((c) => c.key !== 'status'); const logColumns: Column[] = [ { key: 'recipient', header: 'گیرنده', render: (l) => {l.recipient} }, { key: 'message', header: 'پیام', render: (l) => {l.message}, }, { key: 'status', header: 'وضعیت', render: (l) => { const meta = STATUS_LOG_META[l.status] ?? { label: l.status, cls: 'gray' }; return {meta.label}; }, }, { key: 'provider', header: 'سرویس‌دهنده' }, { key: 'sent_at', header: 'زمان ارسال', render: (l) => formatDateTime(l.sent_at) }, ]; const pendingCount = pendingTemplatesQuery.data?.meta?.totalRecords ?? 0; const handleTabChange = (tab: Tab) => { setActiveTab(tab); setPage(1); }; const postVisitPendingCount = (postVisitReviewQuery.data?.data as any)?.data?.length ?? 0; const TABS: { key: Tab; label: string; badge?: number }[] = [ { key: 'samples', label: 'قالب‌های نمونه' }, { key: 'pending', label: 'در انتظار تأیید', badge: pendingCount }, { key: 'post-visit-review', label: 'متن پیامک ویزیت', badge: postVisitPendingCount }, { key: 'logs', label: 'لاگ‌های ارسال' }, ]; return (

پیامک

مدیریت قالب‌های پیامک
{activeTab === 'samples' && ( )}
{/* Tabs */}
{TABS.map((t) => ( ))}
{activeTab === 'samples' && ( <> columns={templateColumns} data={sampleTemplatesQuery.data?.data ?? []} loading={sampleTemplatesQuery.isLoading} emptyMessage="هیچ قالبی یافت نشد" actions={(t) => ( )} /> )} {activeTab === 'pending' && ( <> columns={pendingColumns} data={pendingTemplatesQuery.data?.data ?? []} loading={pendingTemplatesQuery.isLoading} emptyMessage="هیچ قالبی در انتظار تأیید نیست" actions={(t) => ( <> )} /> )} {activeTab === 'post-visit-review' && (
{postVisitReviewQuery.isLoading ? (
در حال بارگذاری...
) : ((postVisitReviewQuery.data?.data as any)?.data ?? []).length === 0 ? (
متنی برای بررسی وجود ندارد
) : (
{((postVisitReviewQuery.data?.data as any)?.data ?? []).map((item: any) => (
{item.entity_type} #{item.entity_id}
{item.post_visit_text_pending}
))}
)}
)} {activeTab === 'logs' && ( <> columns={logColumns} data={logsQuery.data?.data ?? []} loading={logsQuery.isLoading} emptyMessage="لاگی یافت نشد" /> )}
setAddOpen(false)} footer={ <> } >
createMutation.mutate(d))}>
{errors.name &&

{errors.name.message}

}